From 05e13b47059802fd092012b78c2c751f593d5508 Mon Sep 17 00:00:00 2001 From: ctrlaltca Date: Thu, 31 Aug 2023 09:48:04 +0200 Subject: Port the mediaplayer mpris interface to mpris 2.2; fix #1912 (#2552) * Port the mediaplayer mpris interface to mpris 2.2 * Mpris: add strawberry and a generic fallback for other players--- src/modules/mediaplayer/MpMprisInterface.cpp | 474 +++++++++------------------ 1 file changed, 148 insertions(+), 326 deletions(-) (limited to 'src/modules/mediaplayer/MpMprisInterface.cpp') diff --git a/src/modules/mediaplayer/MpMprisInterface.cpp b/src/modules/mediaplayer/MpMprisInterface.cpp index 60bcea8de..0fedd0660 100644 --- a/src/modules/mediaplayer/MpMprisInterface.cpp +++ b/src/modules/mediaplayer/MpMprisInterface.cpp @@ -26,44 +26,9 @@ #if(defined(COMPILE_DBUS_SUPPORT) && !defined(COMPILE_ON_WINDOWS) && !defined(COMPILE_ON_MAC) && !defined(COMPILE_ON_MINGW)) #include "KviLocale.h" -/* - according to MPRIS 1.0, GetStatus returns struct of 4 integers. - - First integer: 0 = Playing, 1 = Paused, 2 = Stopped. - Second integer: 0 = Playing linearly, 1 = Playing randomly. - Third integer: 0 = Go to the next element once the current has finished playing, 1 = Repeat the current element - Fourth integer: 0 = Stop playing once the last element has been played, 1 = Never give up playing -*/ - -struct MPRISPlayerStatus -{ - int Play; - int Random; - int RepeatCurrent; - int RepeatPlaylist; -}; -Q_DECLARE_METATYPE(MPRISPlayerStatus) - -QDBusArgument & operator<<(QDBusArgument & argument, const MPRISPlayerStatus & status) -{ - argument.beginStructure(); - argument << status.Play << status.Random << status.RepeatCurrent << status.RepeatPlaylist; - argument.endStructure(); - return argument; -} - -const QDBusArgument & operator>>(const QDBusArgument & argument, MPRISPlayerStatus & status) -{ - argument.beginStructure(); - argument >> status.Play >> status.Random >> status.RepeatCurrent >> status.RepeatPlaylist; - argument.endStructure(); - return argument; -} - MpMprisInterface::MpMprisInterface() : MpInterface() { - qDBusRegisterMetaType(); } MpMprisInterface::~MpMprisInterface() @@ -82,10 +47,10 @@ int MpMprisInterface::detect(bool) return 1; /* dbus works, player may be closed */ } -#define MPRIS_SIMPLE_CALL(__path, __action) \ - QDBusInterface dbus_iface(m_szServiceName, __path, \ - "org.freedesktop.MediaPlayer", QDBusConnection::sessionBus()); \ - QDBusMessage reply = dbus_iface.call(QDBus::Block, __action); \ +#define MPRIS_SIMPLE_CALL(_path, _action) \ + QDBusInterface dbus_iface(m_szServiceName, "/org/mpris/MediaPlayer2", \ + _path, QDBusConnection::sessionBus()); \ + QDBusMessage reply = dbus_iface.call(QDBus::Block, _action); \ if(reply.type() == QDBusMessage::ErrorMessage) \ { \ QDBusError err = reply; \ @@ -96,98 +61,82 @@ int MpMprisInterface::detect(bool) bool MpMprisInterface::prev() { - MPRIS_SIMPLE_CALL("/Player", "Prev") + MPRIS_SIMPLE_CALL("org.mpris.MediaPlayer2.Player", "Previous") } bool MpMprisInterface::next() { - MPRIS_SIMPLE_CALL("/Player", "Next") + MPRIS_SIMPLE_CALL("org.mpris.MediaPlayer2.Player", "Next") } bool MpMprisInterface::play() { - MPRIS_SIMPLE_CALL("/Player", "Play") + MPRIS_SIMPLE_CALL("org.mpris.MediaPlayer2.Player", "Play") } bool MpMprisInterface::stop() { - MPRIS_SIMPLE_CALL("/Player", "Stop") + MPRIS_SIMPLE_CALL("org.mpris.MediaPlayer2.Player", "Stop") } bool MpMprisInterface::pause() { - MPRIS_SIMPLE_CALL("/Player", "Pause") + MPRIS_SIMPLE_CALL("org.mpris.MediaPlayer2.Player", "Pause") } bool MpMprisInterface::quit() { - MPRIS_SIMPLE_CALL("/", "Quit") + MPRIS_SIMPLE_CALL("org.mpris.MediaPlayer2", "Quit") } -#define MPRIS_CALL_METHOD(__method, __return_if_fail) \ - QDBusInterface dbus_iface(m_szServiceName, "/Player", \ - "org.freedesktop.MediaPlayer", QDBusConnection::sessionBus()); \ - QDBusMessage reply = dbus_iface.call(QDBus::Block, __method); \ - if(reply.type() == QDBusMessage::ErrorMessage) \ - { \ - QDBusError err = reply; \ - qDebug("Error: %s\n%s\n", qPrintable(err.name()), qPrintable(err.message())); \ - return __return_if_fail; \ - } +#define MPRIS_GET_PROPERTY(_property, _return_if_fail) \ + QDBusInterface dbus_iface(m_szServiceName, "/org/mpris/MediaPlayer2", \ + "org.mpris.MediaPlayer2.Player", QDBusConnection::sessionBus()); \ + QVariant reply = dbus_iface.property(_property); \ + if(!reply.isValid()) \ + return _return_if_fail; -#define MPRIS_GET_METADATA_FIELD(__field, __return_type, __return_if_fail) \ +#define MPRIS_GET_METADATA_FIELD(_field, _return_type, _return_if_fail) \ if(this->status() != MpInterface::Playing) \ - return __return_if_fail; \ - MPRIS_CALL_METHOD("GetMetadata", __return_if_fail) \ - foreach(QVariant w, reply.arguments()) \ + return _return_if_fail; \ + MPRIS_GET_PROPERTY("Metadata", _return_if_fail) \ + QVariantMap map = reply.toMap(); \ + foreach (const QString &key, map.keys()) \ { \ - QDBusArgument arg = qvariant_cast(w); \ - QVariant v = qdbus_cast(arg); \ - if(v.userType() == QVariant::Map) \ - { \ - const QVariantMap map = v.toMap(); \ - QVariantMap::ConstIterator it = map.find(__field); \ - if(it != map.end() && it.key() == __field) \ - { \ - return it.value().value<__return_type>(); \ - } \ - } \ + if(key == _field) \ + return map.value(key).value<_return_type>(); \ } \ - return __return_if_fail; - -#define MPRIS_CALL_METHOD_WITH_ARG(__method, __arg, __return_if_fail) \ - QDBusInterface dbus_iface(m_szServiceName, "/Player", \ - "org.freedesktop.MediaPlayer", QDBusConnection::sessionBus()); \ - QDBusMessage reply = dbus_iface.call(QDBus::Block, __method, __arg); \ + return _return_if_fail; + +#define MPRIS_SET_PROPERTY(_property, _arg, _return_if_fail) \ + QDBusInterface dbus_iface(m_szServiceName, "/org/mpris/MediaPlayer2", \ + "org.mpris.MediaPlayer2.Player", QDBusConnection::sessionBus()); \ + if(false == dbus_iface.setProperty(_property, _arg)) \ + return _return_if_fail; + +#define MPRIS_CALL_METHOD_WITH_TWO_ARG(_method, _arg1, _arg2, _return_if_fail) \ + QDBusInterface dbus_iface(m_szServiceName, "/org/mpris/MediaPlayer2", \ + "org.mpris.MediaPlayer2.Player", QDBusConnection::sessionBus()); \ + QDBusMessage reply = dbus_iface.call(QDBus::Block, _method, _arg1, _arg2); \ if(reply.type() == QDBusMessage::ErrorMessage) \ { \ QDBusError err = reply; \ qDebug("Error: %s\n%s\n", qPrintable(err.name()), qPrintable(err.message())); \ - return __return_if_fail; \ + return _return_if_fail; \ } MpInterface::PlayerStatus MpMprisInterface::status() { - MPRIS_CALL_METHOD("GetStatus", MpInterface::Unknown) + MPRIS_GET_PROPERTY("PlaybackStatus", MpInterface::Unknown) - MPRISPlayerStatus status; - - if(reply.arguments().isEmpty()) - return MpInterface::Unknown; - - status = qdbus_cast(reply.arguments().first()); - - switch(status.Play) - { - case 0: - return MpInterface::Playing; - case 1: - return MpInterface::Paused; - case 2: - return MpInterface::Stopped; - default: - return MpInterface::Unknown; - } + QString szStatus = reply.toString(); + if(szStatus == "Playing") + return MpInterface::Playing; + if(szStatus == "Paused") + return MpInterface::Paused; + if(szStatus == "Stopped") + return MpInterface::Stopped; + return MpInterface::Unknown; } QString MpMprisInterface::nowPlaying() @@ -195,27 +144,20 @@ QString MpMprisInterface::nowPlaying() if(this->status() != MpInterface::Playing) return ""; - MPRIS_CALL_METHOD("GetMetadata", "") + MPRIS_GET_PROPERTY("Metadata", "") + QVariantMap map = reply.toMap(); QString artist; QString title; - foreach(QVariant w, reply.arguments()) + foreach (const QString &key, map.keys()) { - QDBusArgument arg = qvariant_cast(w); - QVariant v = qdbus_cast(arg); - if(v.userType() == QVariant::Map) - { - const QVariantMap map = v.toMap(); - QVariantMap::ConstIterator it = map.constBegin(); - for(; it != map.constEnd(); ++it) - { /* maybe do some configurable formatting */ - if(it.key() == "artist") - artist = it.value().toString(); - else if(it.key() == "title") - title = it.value().toString(); - } - } + /* maybe do some configurable formatting */ + if(key == "xesam:artist") + artist = map.value(key).toString(); + else if(key == "xesam:title") + title = map.value(key).toString(); } + if(artist.length() && title.length()) return artist + " - " + title; else @@ -224,110 +166,108 @@ QString MpMprisInterface::nowPlaying() QString MpMprisInterface::mrl() { - MPRIS_CALL_METHOD("GetMetadata", "") - - foreach(QVariant w, reply.arguments()) - { - QDBusArgument arg = qvariant_cast(w); - QVariant v = qdbus_cast(arg); - if(v.userType() == QVariant::Map) - { - const QVariantMap map = v.toMap(); - QVariantMap::ConstIterator it = map.find("location"); - if(it != map.end() && it.key() == "location") - { - return it.value().toString(); - } - } - } - return ""; + MPRIS_GET_METADATA_FIELD("xesam:url", QString, "") } QString MpMprisInterface::title() { - MPRIS_GET_METADATA_FIELD("title", QString, "") + MPRIS_GET_METADATA_FIELD("xesam:title", QString, "") } QString MpMprisInterface::artist() { - MPRIS_GET_METADATA_FIELD("artist", QString, "") + MPRIS_GET_METADATA_FIELD("xesam:artist", QString, "") } QString MpMprisInterface::genre() { - MPRIS_GET_METADATA_FIELD("genre", QString, "") + MPRIS_GET_METADATA_FIELD("xesam:genre", QString, "") } QString MpMprisInterface::comment() { - MPRIS_GET_METADATA_FIELD("comment", QString, "") + MPRIS_GET_METADATA_FIELD("xesam:comment", QString, "") } QString MpMprisInterface::year() { - MPRIS_GET_METADATA_FIELD("year", QString, "") + MPRIS_GET_METADATA_FIELD("xesam:contentCreated", QString, "") } QString MpMprisInterface::album() { - MPRIS_GET_METADATA_FIELD("album", QString, "") + MPRIS_GET_METADATA_FIELD("xesam:album", QString, "") } int MpMprisInterface::bitRate() { + // not-standard MPRIS_GET_METADATA_FIELD("audio-bitrate", int, -1) } int MpMprisInterface::sampleRate() { + // not-standard MPRIS_GET_METADATA_FIELD("audio-samplerate", int, -1) } bool MpMprisInterface::setVol(kvs_int_t & iVol) { - MPRIS_CALL_METHOD_WITH_ARG("VolumeSet", QVariant((int)(100 * iVol / 255)), false); + double dVol = iVol; + MPRIS_SET_PROPERTY("Volume", QVariant(dVol / 255), false); return true; } int MpMprisInterface::getVol() { - MPRIS_CALL_METHOD("VolumeGet", -1) - - int iVol = reply.arguments().first().toInt(); - return iVol * 255 / 100; + MPRIS_GET_PROPERTY("Volume", -1) + return reply.toDouble() * 255; } int MpMprisInterface::position() { - MPRIS_CALL_METHOD("PositionGet", -1) - return reply.arguments().first().toInt(); + MPRIS_GET_PROPERTY("Position", -1) + // from usecs to msecs + return (int) reply.toLongLong() / 1000; } int MpMprisInterface::length() { - MPRIS_CALL_METHOD("GetMetadata", -1) + if(this->status() != MpInterface::Playing) + return -1; - foreach(QVariant w, reply.arguments()) + MPRIS_GET_PROPERTY("Metadata", -1) + QVariantMap map = reply.toMap(); + foreach (const QString &key, map.keys()) { - QDBusArgument arg = qvariant_cast(w); - QVariant v = qdbus_cast(arg); - if(v.userType() == QVariant::Map) - { - const QVariantMap map = v.toMap(); - QVariantMap::ConstIterator it = map.constBegin(); - for(; it != map.constEnd(); ++it) - { - if(it.key() == "mtime") - return it.value().toInt(); - } - } + // from usecs to msecs + if(key == "mpris:length") + return (int) map.value(key).toLongLong() / 1000; } + return -1; } +QVariant MpMprisInterface::getTrackId() +{ + if(this->status() != MpInterface::Playing) + return QVariant(); + + MPRIS_GET_PROPERTY("Metadata", QVariant()) + QVariantMap map = reply.toMap(); + + return map.value("mpris:trackid"); +} + bool MpMprisInterface::jumpTo(kvs_int_t & iPos) { - MPRIS_CALL_METHOD_WITH_ARG("PositionSet", QVariant((int)iPos), false) + QVariant oTrackId = getTrackId(); + if(!oTrackId.isValid()) + return false; + + qlonglong llPos = iPos; + // from usecs to msecs + MPRIS_CALL_METHOD_WITH_TWO_ARG("SetPosition", oTrackId, QVariant(llPos * 1000), false) return true; } @@ -343,147 +283,7 @@ MP_IMPLEMENT_DESCRIPTOR( MpAudaciousInterface::MpAudaciousInterface() : MpMprisInterface() { - m_szServiceName = "org.mpris.audacious"; -} - -int MpAudaciousInterface::getPlayListPos() -{ - QDBusInterface dbus_iface("org.mpris.audacious", "/org/atheme/audacious", - "org.atheme.audacious", QDBusConnection::sessionBus()); - QDBusReply pos = dbus_iface.call(QDBus::Block, "Position"); - return pos; -} - -bool MpAudaciousInterface::quit() -{ - if(MpMprisInterface::quit()) - return true; - - /* compatibility with older versions */ - MPRIS_SIMPLE_CALL("/Player", "Quit") -} - -QString MpAudaciousInterface::mrl() -{ - MPRIS_CALL_METHOD("GetMetadata", "") - - foreach(QVariant w, reply.arguments()) - { - QDBusArgument arg = qvariant_cast(w); - QVariant v = qdbus_cast(arg); - if(v.userType() == QVariant::Map) - { - const QVariantMap map = v.toMap(); - QVariantMap::ConstIterator it = map.find("location"); - if(it != map.end() && it.key() == "location") - { - return it.value().toString(); - } - /* Some audacious versions send URI instead of location */ - it = map.find("URI"); - if(it != map.end() && it.key() == "URI") - { - return it.value().toString(); - } - } - } - return ""; -} - -MpInterface::PlayerStatus MpAudaciousInterface::status() -{ - MpInterface::PlayerStatus status; - status = MpMprisInterface::status(); - if(status != MpInterface::Unknown) - return status; - - /* compatibility with older versions */ - QDBusInterface dbus_iface(m_szServiceName, "/Player", - "org.freedesktop.MediaPlayer", QDBusConnection::sessionBus()); - if(!dbus_iface.isValid()) - return MpInterface::Unknown; - - QDBusMessage reply = dbus_iface.call(QDBus::Block, "GetStatus"); - - switch(reply.arguments().first().toInt()) - { - case 0: - return MpInterface::Playing; - case 1: - return MpInterface::Paused; - case 2: - return MpInterface::Stopped; - default: - return MpInterface::Unknown; - } -} - -int MpAudaciousInterface::length() -{ - int length = MpMprisInterface::length(); - if(length != -1) - return length; - - /* compatibility with older versions */ - MPRIS_CALL_METHOD("GetMetadata", -1) - - foreach(QVariant w, reply.arguments()) - { - QDBusArgument arg = qvariant_cast(w); - QVariant v = qdbus_cast(arg); - if(v.userType() == QVariant::Map) - { - const QVariantMap map = v.toMap(); - QVariantMap::ConstIterator it = map.constBegin(); - for(; it != map.constEnd(); ++it) - { - if(it.key() == "length") - return it.value().toInt(); - } - } - } - return -1; -} - -#define AUDACIOUS_GET_TUPLE_FIELD(__field) \ - if(this->status() != MpInterface::Playing) \ - return ""; \ - QDBusInterface dbus_iface("org.mpris.audacious", "/org/atheme/audacious", \ - "org.atheme.audacious", QDBusConnection::sessionBus()); \ - QList args; \ - args << (uint) this->getPlayListPos() << QString(__field); \ - QDBusReply reply = dbus_iface.callWithArgumentList(QDBus::Block, "SongTuple", args); \ - return reply.value().variant().toString(); - -QString MpAudaciousInterface::year() -{ - AUDACIOUS_GET_TUPLE_FIELD("year") -} - -QString MpAudaciousInterface::mediaType() -{ - AUDACIOUS_GET_TUPLE_FIELD("codec") -} - -/* BMPx interface */ -MP_IMPLEMENT_DESCRIPTOR( - MpBmpxInterface, - "bmpx", - __tr2qs_ctx( - "An interface for BMPx.\n" - "Download it from http://sourceforge.net/projects/beepmp\n", - "mediaplayer")) - -MpBmpxInterface::MpBmpxInterface() - : MpMprisInterface() -{ - m_szServiceName = "org.mpris.bmp"; -} - -/* BMPx doesn't provide GetStatus method, always assume Playing status */ -MpInterface::PlayerStatus MpBmpxInterface::status() -{ - return MpInterface::Playing; + m_szServiceName = "org.mpris.MediaPlayer2.audacious"; } /* Amarok2 interface */ @@ -498,7 +298,7 @@ MP_IMPLEMENT_DESCRIPTOR( MpAmarok2Interface::MpAmarok2Interface() : MpMprisInterface() { - m_szServiceName = "org.mpris.amarok"; + m_szServiceName = "org.mpris.MediaPlayer2.amarok"; } /* Qmmp interface */ @@ -513,7 +313,7 @@ MP_IMPLEMENT_DESCRIPTOR( MpQmmpInterface::MpQmmpInterface() : MpMprisInterface() { - m_szServiceName = "org.mpris.qmmp"; + m_szServiceName = "org.mpris.MediaPlayer2.qmmp"; } /* xmms2 interface */ @@ -528,29 +328,7 @@ MP_IMPLEMENT_DESCRIPTOR( MpXmms2Interface::MpXmms2Interface() : MpMprisInterface() { - m_szServiceName = "org.mpris.xmms2"; -} - -/* mozilla songbird interface */ -MP_IMPLEMENT_DESCRIPTOR( - MpSongbirdInterface, - "songbird", - __tr2qs_ctx( - "An interface for the Mozilla Songbird media player.\n" - "Download it from http://www.getsongbird.com.\n" - "To use it you have to install also the MPRIS addon " - "available at http://addons.songbirdnest.com/addon/1626.\n", - "mediaplayer")) - -MpSongbirdInterface::MpSongbirdInterface() - : MpMprisInterface() -{ - m_szServiceName = "org.mpris.songbird"; -} - -MpInterface::PlayerStatus MpSongbirdInterface::status() -{ - return MpInterface::Playing; + m_szServiceName = "org.mpris.MediaPlayer2.xmms2"; } /* Totem interface */ @@ -565,7 +343,7 @@ MP_IMPLEMENT_DESCRIPTOR( MpTotemInterface::MpTotemInterface() : MpMprisInterface() { - m_szServiceName = "org.mpris.Totem"; + m_szServiceName = "org.mpris.MediaPlayer2.Totem"; } /* Vlc interface */ @@ -582,7 +360,7 @@ MP_IMPLEMENT_DESCRIPTOR( MpVlcInterface::MpVlcInterface() : MpMprisInterface() { - m_szServiceName = "org.mpris.vlc"; + m_szServiceName = "org.mpris.MediaPlayer2.vlc"; } /* Clementine interface */ @@ -597,6 +375,50 @@ MP_IMPLEMENT_DESCRIPTOR( MpClementineInterface::MpClementineInterface() : MpMprisInterface() { - m_szServiceName = "org.mpris.clementine"; + m_szServiceName = "org.mpris.MediaPlayer2.clementine"; +} + +/* Strawberry interface */ +MP_IMPLEMENT_DESCRIPTOR( + MpStrawberryInterface, + "strawberry", + __tr2qs_ctx( + "An interface for Strawberry.\n" + "Download it from https://www.strawberrymusicplayer.org/\n", + "mediaplayer")) + +MpStrawberryInterface::MpStrawberryInterface() + : MpMprisInterface() +{ + m_szServiceName = "org.mpris.MediaPlayer2.strawberry"; +} + +/* Generic Mpris interface */ +MP_IMPLEMENT_DESCRIPTOR( + MpMprisGenericInterface, + "generic mpris 2", + __tr2qs_ctx( + "An generic interface for MPRIS 2 media players.", + "mediaplayer")) + +MpMprisGenericInterface::MpMprisGenericInterface() + : MpMprisInterface() +{ +} + +int MpMprisGenericInterface::detect(bool) +{ + QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); + if(!reply.isValid()) /* something fishy with dbus, it won't work */ + return 0; + + foreach(QString name, reply.value()) + if(name.startsWith("org.mpris.MediaPlayer2.")) { + m_szServiceName = name; + // prefer the player-specific interfaces + return 99; + } + + return 1; /* dbus works, player may be closed */ } #endif //COMPILE_ON_WINDOWS -- cgit v1.3.1-10-gc9f91