awesome-widgets/sources/extsysmonsources/playersource.cpp
Evgeniy Alekseev acc4df92bc fix invalid mpris parser
In some cases it causes the error like:

Error message "Интерфейс «(null)» для пути /org/mpris/MediaPlayer2 объекта не найден"

(for example deadbeef was affected). Suggested patch should fix this
issue and seems to be working with at least deadbeef and qmmp mpris
engines.
2017-05-17 17:43:18 +03:00

400 lines
14 KiB
C++

/***************************************************************************
* This file is part of awesome-widgets *
* *
* awesome-widgets is free software: you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* awesome-widgets is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with awesome-widgets. If not, see http://www.gnu.org/licenses/ *
***************************************************************************/
#include "playersource.h"
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusMessage>
#include <QProcess>
#include <QTextCodec>
#include "awdebug.h"
PlayerSource::PlayerSource(QObject *_parent, const QStringList &_args)
: AbstractExtSysMonSource(_parent, _args)
{
Q_ASSERT(_args.count() == 5);
qCDebug(LOG_ESS) << __PRETTY_FUNCTION__;
m_player = _args.at(0);
m_mpdAddress = _args.at(1);
m_mpdPort = _args.at(2).toInt();
m_mpris = _args.at(3);
m_symbols = _args.at(4).toInt();
connect(&m_mpdSocket, SIGNAL(connected()), this,
SLOT(mpdSocketConnected()));
connect(&m_mpdSocket, SIGNAL(readyRead()), this,
SLOT(mpdSocketReadyRead()));
connect(&m_mpdSocket, SIGNAL(bytesWritten(qint64)), this,
SLOT(mpdSocketWritten(const qint64)));
m_mpdCached = defaultInfo();
}
PlayerSource::~PlayerSource()
{
qCDebug(LOG_ESS) << __PRETTY_FUNCTION__;
m_mpdSocket.close();
}
QVariant PlayerSource::data(const QString &_source)
{
qCDebug(LOG_ESS) << "Source" << _source;
if (!m_values.contains(_source))
run();
QVariant value = m_values.take(_source);
return value;
}
QString PlayerSource::getAutoMpris() const
{
QDBusMessage listServices = QDBusConnection::sessionBus().interface()->call(
QDBus::BlockWithGui, "ListNames");
if (listServices.arguments().isEmpty()) {
qCWarning(LOG_ESS) << "Could not find any DBus service";
return "";
}
QStringList arguments = listServices.arguments().first().toStringList();
for (auto &arg : arguments) {
if (!arg.startsWith("org.mpris.MediaPlayer2."))
continue;
qCInfo(LOG_ESS) << "Service found" << arg;
QString service = arg;
service.remove("org.mpris.MediaPlayer2.");
return service;
}
return "";
}
QVariantMap PlayerSource::initialData(const QString &_source) const
{
qCDebug(LOG_ESS) << "Source" << _source;
QVariantMap data;
if (_source == "player/album") {
data["min"] = "";
data["max"] = "";
data["name"] = "Current song album";
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/salbum") {
data["min"] = "";
data["max"] = "";
data["name"]
= QString("Current song album (%1 symbols)").arg(m_symbols);
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/dalbum") {
data["min"] = "";
data["max"] = "";
data["name"] = QString("Current song album (%1 symbols, dynamic)")
.arg(m_symbols);
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/artist") {
data["min"] = "";
data["max"] = "";
data["name"] = "Current song artist";
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/sartist") {
data["min"] = "";
data["max"] = "";
data["name"]
= QString("Current song artist (%1 symbols)").arg(m_symbols);
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/dartist") {
data["min"] = "";
data["max"] = "";
data["name"] = QString("Current song artist (%1 symbols, dynamic)")
.arg(m_symbols);
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/duration") {
data["min"] = 0;
data["max"] = 0;
data["name"] = "Current song duration";
data["type"] = "integer";
data["units"] = "s";
} else if (_source == "player/progress") {
data["min"] = 0;
data["max"] = 0;
data["name"] = "Current song progress";
data["type"] = "integer";
data["units"] = "s";
} else if (_source == "player/title") {
data["min"] = "";
data["max"] = "";
data["name"] = "Current song title";
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/stitle") {
data["min"] = "";
data["max"] = "";
data["name"]
= QString("Current song title (%1 symbols)").arg(m_symbols);
data["type"] = "QString";
data["units"] = "";
} else if (_source == "player/dtitle") {
data["min"] = "";
data["max"] = "";
data["name"] = QString("Current song title (%1 symbols, dynamic)")
.arg(m_symbols);
data["type"] = "QString";
data["units"] = "";
}
return data;
}
void PlayerSource::run()
{
// initial data
if (m_player == "mpd") {
// mpd
m_values = getPlayerMpdInfo();
} else if (m_player == "mpris") {
// players which supports mpris
if (m_dbusMutex.tryLock()) {
QString mpris = m_mpris == "auto" ? getAutoMpris() : m_mpris;
m_values = getPlayerMprisInfo(mpris);
m_dbusMutex.unlock();
}
}
// dymanic properties
// solid
m_values["player/salbum"]
= stripString(m_values["player/album"].toString(), m_symbols);
m_values["player/sartist"]
= stripString(m_values["player/artist"].toString(), m_symbols);
m_values["player/stitle"]
= stripString(m_values["player/title"].toString(), m_symbols);
// dynamic
m_values["player/dalbum"]
= buildString(m_values["player/dalbum"].toString(),
m_values["player/album"].toString(), m_symbols);
m_values["player/dartist"]
= buildString(m_values["player/dartist"].toString(),
m_values["player/artist"].toString(), m_symbols);
m_values["player/dtitle"]
= buildString(m_values["player/dtitle"].toString(),
m_values["player/title"].toString(), m_symbols);
}
QStringList PlayerSource::sources() const
{
QStringList sources;
sources.append("player/album");
sources.append("player/dalbum");
sources.append("player/salbum");
sources.append("player/artist");
sources.append("player/dartist");
sources.append("player/sartist");
sources.append("player/duration");
sources.append("player/progress");
sources.append("player/title");
sources.append("player/dtitle");
sources.append("player/stitle");
return sources;
}
QString PlayerSource::buildString(const QString &_current,
const QString &_value, const int _s)
{
qCDebug(LOG_ESS) << "Current value" << _current << "received" << _value
<< "will be stripped after" << _s;
int index = _value.indexOf(_current);
if ((_current.isEmpty()) || ((index + _s + 1) > _value.count()))
return QString("%1").arg(_value.left(_s), -_s, QLatin1Char(' '));
else
return QString("%1").arg(_value.mid(index + 1, _s), -_s,
QLatin1Char(' '));
}
QString PlayerSource::stripString(const QString &_value, const int _s)
{
qCDebug(LOG_ESS) << "New value" << _value << "will be stripped after" << _s;
return _value.count() > _s ? QString("%1\u2026").arg(_value.left(_s - 1))
: _value.leftJustified(_s, QLatin1Char(' '));
}
bool PlayerSource::isMpdSocketConnected() const
{
return (m_mpdSocket.state() == QAbstractSocket::ConnectedState);
}
void PlayerSource::mpdSocketConnected()
{
qCDebug(LOG_ESS) << "MPD socket connected to" << m_mpdSocket.peerName()
<< "with state" << m_mpdSocket.state();
}
void PlayerSource::mpdSocketReadyRead()
{
QString qoutput = QTextCodec::codecForMib(106)
->toUnicode(m_mpdSocket.readAll())
.trimmed();
qCInfo(LOG_ESS) << "Output" << qoutput;
// parse
for (auto &str : qoutput.split('\n', QString::SkipEmptyParts)) {
if (str.split(": ", QString::SkipEmptyParts).count() == 2) {
// "Metadata: data"
QString metadata
= str.split(": ", QString::SkipEmptyParts).first().toLower();
QString data
= str.split(": ", QString::SkipEmptyParts).last().trimmed();
// there are one more time...
if ((metadata == "time") && (data.contains(':'))) {
QStringList times = data.split(':');
m_mpdCached["player/duration"] = times.at(0).toInt();
m_mpdCached["player/progress"] = times.at(1).toInt();
} else if (m_metadata.contains(metadata)) {
m_mpdCached[QString("player/%1").arg(metadata)] = data;
}
}
}
emit(dataReceived(m_mpdCached));
}
void PlayerSource::mpdSocketWritten(const qint64 _bytes)
{
qCDebug(LOG_ESS) << "Bytes written" << _bytes << "to"
<< m_mpdSocket.peerName();
}
QVariantHash PlayerSource::defaultInfo() const
{
QVariantHash info;
info["player/album"] = "unknown";
info["player/artist"] = "unknown";
info["player/duration"] = 0;
info["player/progress"] = 0;
info["player/title"] = "unknown";
return info;
}
QVariantHash PlayerSource::getPlayerMpdInfo()
{
if (m_mpdSocket.state() == QAbstractSocket::UnconnectedState) {
// connect to host
qCInfo(LOG_ESS) << "Connect to" << m_mpdAddress << m_mpdPort;
m_mpdSocket.connectToHost(m_mpdAddress, m_mpdPort);
} else if (m_mpdSocket.state() == QAbstractSocket::ConnectedState) {
// send request
if (m_mpdSocket.write(MPD_STATUS_REQUEST) == -1)
qCWarning(LOG_ESS)
<< "Could not write request to" << m_mpdSocket.peerName();
}
return m_mpdCached;
}
QVariantHash PlayerSource::getPlayerMprisInfo(const QString &_mpris) const
{
qCDebug(LOG_ESS) << "MPRIS" << _mpris;
QVariantHash info = defaultInfo();
if (_mpris.isEmpty())
return info;
QDBusConnection bus = QDBusConnection::sessionBus();
// comes from the following request:
// qdbus org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2
// org.freedesktop.DBus.Properties.Get org.mpris.MediaPlayer2.Player
// Metadata
// or the same but using dbus-send:
// dbus-send --print-reply --session --dest=org.mpris.MediaPlayer2.vlc
// /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get
// string:'org.mpris.MediaPlayer2.Player' string:'Metadata'
QVariantList args
= QVariantList({"org.mpris.MediaPlayer2.Player", "Metadata"});
QDBusMessage request = QDBusMessage::createMethodCall(
QString("org.mpris.MediaPlayer2.%1").arg(_mpris),
"/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
request.setArguments(args);
QDBusMessage response
= bus.call(request, QDBus::BlockWithGui, REQUEST_TIMEOUT);
if ((response.type() != QDBusMessage::ReplyMessage)
|| (response.arguments().isEmpty())) {
qCWarning(LOG_ESS) << "Error message" << response.errorMessage();
} else {
// another portion of dirty magic
QVariantHash map
= qdbus_cast<QVariantHash>(response.arguments()
.first()
.value<QDBusVariant>()
.variant()
.value<QDBusArgument>());
info["player/album"] = map.value("xesam:album", "unknown");
// artist is array
info["player/artist"] = map.value("xesam:artist", "unknown").toString();
info["player/duration"]
= map.value("mpris:length", 0).toInt() / (1000 * 1000);
info["player/title"] = map.value("xesam:title", "unknown");
}
// position
args[1] = "Position";
request.setArguments(args);
response = bus.call(request, QDBus::BlockWithGui);
if ((response.type() != QDBusMessage::ReplyMessage)
|| (response.arguments().isEmpty())) {
qCWarning(LOG_ESS) << "Error message" << response.errorMessage();
} else {
// this cast is simpler than the previous one ;)
info["player/progress"] = response.arguments()
.first()
.value<QDBusVariant>()
.variant()
.toLongLong()
/ (1000 * 1000);
}
return info;
}