Commit 793c07f3 authored by Jonathan Marten's avatar Jonathan Marten

MPRIS2: Get the icon name from the player application

Previously there was a hardcoded list of applications in
Mixer_MPRIS2::getChannelTypeFromPlayerId() which passed special
MixDevice::ChannelType values to MixDevice::channelTypeToIconName().
Now the icon name is found via the application's desktop file, which
is requested from the running application via DBus, and passed directly
to MixDevice.  Icons for audio devices are still managed by MixDevice.

There are some applications which do not provide the DBus interface
to locate their desktop file.  Therefore, there is still a hardcoded
fallback list in Mixer_MPRIS2::getIconNameFromPlayerId() which provides
an icon name.  The MixDevice::ChannelType values for specific
applications are no longer used.
parent 19c4bfcf
......@@ -17,6 +17,7 @@
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* MPRIS2 specification: https://specifications.freedesktop.org/mpris-spec/
*/
#include "mixer_mpris2.h"
......@@ -25,12 +26,11 @@
#include "core/GlobalConfig.h"
#include "kmix_debug.h"
#include <QStringList>
#include <QDBusReply>
#include <QString>
#include <qvariant.h>
#include <klocalizedstring.h>
#include <kdesktopfile.h>
// Set the QDBUS_DEBUG env variable for debugging Qt DBUS calls.
......@@ -86,9 +86,9 @@ int Mixer_MPRIS2::mediaNext(QString id)
*/
int Mixer_MPRIS2::mediaControl(QString applicationId, QString commandName)
{
MPrisControl* mad = controls.value(applicationId);
if ( mad == 0 )
return 0; // Might have disconnected recently => simply ignore command
MPrisControl *mad = controls.value(applicationId);
if (mad==nullptr)
return (0); // Might have disconnected recently => simply ignore command
qCDebug(KMIX_LOG) << "Send " << commandName << " to id=" << applicationId;
QDBusPendingReply<> repl2 =
......@@ -98,13 +98,13 @@ int Mixer_MPRIS2::mediaControl(QString applicationId, QString commandName)
QDBusPendingCallWatcher* watchMediaControlReply = new QDBusPendingCallWatcher(repl2, mad);
connect(watchMediaControlReply, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherMediaControl(QDBusPendingCallWatcher*)));
return 0; // Presume everything went well. Can't do more for ASYNC calls
return (0); // Presume everything went well. Can't do more for ASYNC calls
}
void Mixer_MPRIS2::watcherMediaControl(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
MPrisControl *mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl==nullptr)
{
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
}
......@@ -195,8 +195,8 @@ signal sender=:1.125 -> dest=(null destination) serial=503 path=/org/mpris/Media
*/
int Mixer_MPRIS2::writeVolumeToHW( const QString& id, shared_ptr<MixDevice> md )
{
Volume& vol = md->playbackVolume();
double volFloat = 0;
const Volume &vol = md->playbackVolume();
double volFloat = 0.0;
if ( ! md->isMuted() )
{
int volInt = vol.getVolume(Volume::LEFT);
......@@ -218,7 +218,6 @@ int Mixer_MPRIS2::writeVolumeToHW( const QString& id, shared_ptr<MixDevice> md )
QVariant v1 = QVariant(QString("org.mpris.MediaPlayer2.Player"));
QVariant v2 = QVariant(QString("Volume"));
QVariant v3 = QVariant::fromValue(QDBusVariant(volFloat));
// QVariant v3 = QVariant(volFloat);
// I don't care too much for the reply, as I won't receive a result. Thus fire-and-forget here.
mad->propertyIfc->asyncCall("Set", v1, v2, v3);
......@@ -349,44 +348,39 @@ void Mixer_MPRIS2::addMprisControlAsync(QString busDestination)
QDBusPendingReply<QVariant > repl2 = mad->propertyIfc->asyncCall("Get", v1, v2);
QDBusPendingCallWatcher* watchIdentity = new QDBusPendingCallWatcher(repl2, mad);
connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherPlugControlId(QDBusPendingCallWatcher*)));
v2 = QVariant(QString("DesktopEntry"));
repl2 = mad->propertyIfc->asyncCall("Get", v1, v2);
watchIdentity = new QDBusPendingCallWatcher(repl2, mad);
connect(watchIdentity, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(watcherDesktopFile(QDBusPendingCallWatcher*)));
}
MixDevice::ChannelType Mixer_MPRIS2::getChannelTypeFromPlayerId(const QString& id)
// These icon names were originally provided by MixDevice::channelTypeToIconName()
// using MixDevice::ChannelType values hardcoded for known applications in
// Mixer_MPRIS2::getChannelTypeFromPlayerId(). Now the icon name is resolved
// here only for those applications which are not expected to provide an icon
// name in their desktop file, or do not make that information available.
static QString getIconNameFromPlayerId(const QString &id)
{
// TODO This hardcoded application list is a quick hack. It should be generalized.
MixDevice::ChannelType ct = MixDevice::APPLICATION_STREAM;
if (id.startsWith(QLatin1String("amarok")))
{
ct = MixDevice::APPLICATION_AMAROK;
}
else if (id.startsWith(QLatin1String("banshee")))
{
ct = MixDevice::APPLICATION_BANSHEE;
}
else if (id.startsWith(QLatin1String("vlc")))
{
ct = MixDevice::APPLICATION_VLC;
}
else if (id.startsWith(QLatin1String("xmms")))
{
ct = MixDevice::APPLICATION_XMM2;
}
else if (id.startsWith(QLatin1String("tomahawk")))
{
ct = MixDevice::APPLICATION_TOMAHAWK;
}
else if (id.startsWith(QLatin1String("clementine")))
{
ct = MixDevice::APPLICATION_CLEMENTINE;
}
if (id.startsWith(QLatin1String("amarok"))) return ("amarok");
if (id.startsWith(QLatin1String("banshee"))) return ("media-player-banshee");
if (id.startsWith(QLatin1String("xmms"))) return ("xmms");
if (id.startsWith(QLatin1String("tomahawk"))) return ("tomahawk");
if (id.startsWith(QLatin1String("clementine"))) return ("application-x-clementine");
// Surprisingly...
if (id.startsWith(QLatin1String("chrome"))) return ("chrome-browser");
if (id.startsWith(QLatin1String("chromium"))) return ("chromium-browser");
return ct;
return (QString()); // no application known
}
void Mixer_MPRIS2::watcherInitialVolume(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
MPrisControl *mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl==nullptr)
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
const QDBusMessage& msg = watcher->reply();
......@@ -404,8 +398,8 @@ void Mixer_MPRIS2::watcherInitialVolume(QDBusPendingCallWatcher* watcher)
void Mixer_MPRIS2::watcherInitialPlayState(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
MPrisControl *mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl==nullptr)
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
const QDBusMessage& msg = watcher->reply();
......@@ -423,6 +417,40 @@ void Mixer_MPRIS2::watcherInitialPlayState(QDBusPendingCallWatcher* watcher)
watcher->deleteLater();
}
void Mixer_MPRIS2::watcherDesktopFile(QDBusPendingCallWatcher *watcher)
{
MPrisControl *mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl==nullptr) return;
const QDBusMessage& msg = watcher->reply();
QList<QVariant> repl = msg.arguments();
if (!repl.isEmpty())
{
QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(repl.at(0));
QVariant result2 = dbusVariant.variant();
KDesktopFile desktop(result2.toString()+".desktop");
if (desktop.hasApplicationType())
{
QString iconName = desktop.readIcon();
shared_ptr<MixDevice> md = m_mixDevices.get(mprisCtl->getId());
qDebug() << "got icon" << iconName << "for application" << md->readableName();
md->setIconName(iconName);
const QString &builtinIconName = getIconNameFromPlayerId(md->id());
if (!builtinIconName.isEmpty())
{
qCWarning(KMIX_LOG) << "The MPRIS2 application" << md->id()
<< "provides an icon name via its desktop file."
<< "It does not need to be hardcoded"
<< "in Mixer_MPRIS2::getIconNameFromPlayerId().";
}
}
}
watcher->deleteLater();
}
/**
* Convenience method for the watcher*() methods.
......@@ -440,29 +468,25 @@ MPrisControl* Mixer_MPRIS2::watcherHelperGetMPrisControl(QDBusPendingCallWatcher
const QDBusMessage& msg = watcher->reply();
if ( msg.type() == QDBusMessage::ReplyMessage )
{
QObject* obj = watcher->parent();
MPrisControl* mad = qobject_cast<MPrisControl*>(obj);
if (mad != 0)
{
return mad;
}
QObject *obj = watcher->parent();
MPrisControl *mad = qobject_cast<MPrisControl *>(obj);
if (mad!=nullptr) return (mad);
qCWarning(KMIX_LOG) << "Ignoring unexpected Control Id. object=" << obj;
}
else if ( msg.type() == QDBusMessage::ErrorMessage )
{
qCCritical(KMIX_LOG) << "ERROR in Media control operation, path=" << msg.path() << ", msg=" << msg;
qCCritical(KMIX_LOG) << "ERROR in Media control operation, path=" << msg.path() << "msg=" << msg;
}
watcher->deleteLater();
return 0;
return (nullptr);
}
void Mixer_MPRIS2::watcherPlugControlId(QDBusPendingCallWatcher* watcher)
{
MPrisControl* mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl == 0)
MPrisControl *mprisCtl = watcherHelperGetMPrisControl(watcher);
if (mprisCtl==nullptr)
{
return; // Reply for unknown media player. Probably "unplugged" (or not yet plugged)
}
......@@ -481,16 +505,22 @@ void Mixer_MPRIS2::watcherPlugControlId(QDBusPendingCallWatcher* watcher)
QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(repl.at(0));
QVariant result2 = dbusVariant.variant();
readableName = result2.toString();
//qCDebug(KMIX_LOG) << "REPLY " << result2.type() << ": " << readableName;
QString iconName = getIconNameFromPlayerId(id);
// This is the icon name returned for MixDevice::APPLICATION_STREAM
// by MixDevice::channelTypeToIconName(). If the application
// provides an icon name in its desktop file, then this is only a
// fallback until that information arrives (asynchronously) over DBus.
if (iconName.isEmpty()) iconName = "mixer-pcm";
// qCDebug(KMIX_LOG) << "REPLY " << result2.type() << ": " << readableName;
MixDevice *mdNew = new MixDevice(_mixer, id, readableName, iconName);
MixDevice::ChannelType ct = getChannelTypeFromPlayerId(id);
MixDevice* mdNew = new MixDevice(_mixer, id, readableName, ct);
// MPRIS2 doesn't support an actual mute switch. Mute is defined as volume = 0.0
// Thus we won't add the playback switch
Volume* vol = new Volume( 100, 0, false, false);
Volume *vol = new Volume( 100, 0, false, false);
vol->addVolumeChannel(VolumeChannel(Volume::LEFT)); // MPRIS is only one control ("Mono")
MediaController* mediaContoller = mdNew->mediaController();
MediaController *mediaContoller = mdNew->mediaController();
mediaContoller->addMediaPlayControl();
mediaContoller->addMediaNextControl();
mediaContoller->addMediaPrevControl();
......
......@@ -152,6 +152,7 @@ private slots:
void watcherPlugControlId(QDBusPendingCallWatcher* watcher);
void watcherInitialVolume(QDBusPendingCallWatcher* watcher);
void watcherInitialPlayState(QDBusPendingCallWatcher* watcher);
void watcherDesktopFile(QDBusPendingCallWatcher* watcher);
private:
// Helpers for the watchers
......@@ -164,7 +165,6 @@ private:
int addAllRunningPlayersAndInitHotplug();
void volumeChangedInternal(shared_ptr<MixDevice> md, int volumePercentage);
QString busDestinationToControlId(const QString& busDestination);
MixDevice::ChannelType getChannelTypeFromPlayerId(const QString& id);
QMap<QString,MPrisControl*> controls;
QString _id;
......
......@@ -80,18 +80,10 @@ static const QString channelTypeToIconName( MixDevice::ChannelType type )
case MixDevice::KMIX_COMPOSITE:
return "mixer-line";
case MixDevice::APPLICATION_AMAROK:
return "amarok";
case MixDevice::APPLICATION_BANSHEE:
return "media-player-banshee";
case MixDevice::APPLICATION_XMM2:
return "xmms";
case MixDevice::APPLICATION_TOMAHAWK:
return "tomahawk";
case MixDevice::APPLICATION_CLEMENTINE:
return "application-x-clementine";
case MixDevice::APPLICATION_VLC:
return "vlc";
// Icon names for known MPRIS2 applications are now taken from the application's
// desktop file, for those that provide one. If the application does not
// provide a desktop file or icon information then there is a fallback list
// in Mixer_MPRIS2::getIconNameFromPlayerId().
case MixDevice::APPLICATION_STREAM:
return "mixer-pcm";
......@@ -432,3 +424,11 @@ int MixDevice::userVolumeLevel() const
int val = isActive ? vol.getAvgVolumePercent(Volume::MALL) : 0;
return (val);
}
void MixDevice::setIconName(const QString &newName)
{
qDebug() << "for" << readableName() << "icon" << newName;
_iconName = newName;
emit iconNameChanged(newName);
}
......@@ -90,19 +90,9 @@ public:
MICROPHONE_BOOST,
MICROPHONE_FRONT_BOOST,
MICROPHONE_FRONT,
KMIX_COMPOSITE,
APPLICATION_STREAM,
// Some specific applications
APPLICATION_AMAROK,
APPLICATION_BANSHEE,
APPLICATION_XMM2,
APPLICATION_TOMAHAWK,
APPLICATION_CLEMENTINE,
// Hint: VLC still has compatibility problems:
// 2.0 is not detected
// 2.2-nightly has volume issues (total overdrive)
APPLICATION_VLC,
APPLICATION_STREAM
};
enum SwitchType { OnOff, Mute, Capture, Activator };
......@@ -132,6 +122,7 @@ public:
shared_ptr<MixDevice> addToPool();
const QString &iconName() const { return (_iconName); }
void setIconName(const QString &newName);
void addPlaybackVolume(Volume &playbackVol);
void addCaptureVolume (Volume &captureVol);
......@@ -222,6 +213,9 @@ public:
protected:
void init( Mixer* mixer, const QString& id, const QString& name, const QString& iconName, MixSet* moveDestinationMixSet );
signals:
void iconNameChanged(const QString &newName);
private:
QString getVolString(Volume::ChannelID chid, bool capture);
......
......@@ -261,6 +261,17 @@ void MDWSlider::guiAddControlIcon(const QString &tooltipText)
ToggleToolButton::setIndicatorIcon(mixDevice()->iconName(), m_controlIcon);
m_controlIcon->setToolTip(tooltipText);
m_controlIcon->installEventFilter(this);
// A MPRIS2 application's icon name is obtained from its desktop file.
// Finding the desktop file is an asynchronous DBus operation which
// could in theory happen after the MDWSlider has been created (which
// would have used the fallback icon name). So update the indicator
// icon on a signal.
connect(mixDevice().get(), &MixDevice::iconNameChanged,
this, [this](const QString &newName) {
qDebug() << "for" << mixDevice()->readableName() << "new icon" << newName;
ToggleToolButton::setIndicatorIcon(newName, m_controlIcon);
});
}
QWidget *MDWSlider::guiAddButtonSpacer()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment