/*************************************************************************** * 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 #include #include #include #include #include #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 = QString("%1:%2").arg(args.at(1)).arg(args.at(2)); m_mpris = args.at(3); m_symbols = args.at(4).toInt(); m_mpdProcess = new QProcess(nullptr); // fucking magic from http://doc.qt.io/qt-5/qprocess.html#finished connect(m_mpdProcess, static_cast( &QProcess::finished), [this](int, QProcess::ExitStatus) { return updateMpdValue(); }); m_mpdProcess->waitForFinished(0); m_mpdCached = defaultInfo(); } PlayerSource::~PlayerSource() { qCDebug(LOG_ESS) << __PRETTY_FUNCTION__; m_mpdProcess->kill(); m_mpdProcess->deleteLater(); } QVariant PlayerSource::data(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, QString("ListNames")); if (listServices.arguments().isEmpty()) return QString(); QStringList arguments = listServices.arguments().first().toStringList(); for (auto arg : arguments) { if (!arg.startsWith(QString("org.mpris.MediaPlayer2."))) continue; qCInfo(LOG_ESS) << "Service found" << arg; QString service = arg; service.remove(QString("org.mpris.MediaPlayer2.")); return service; } return QString(); } QVariantMap PlayerSource::initialData(QString source) const { qCDebug(LOG_ESS) << "Source" << source; QVariantMap data; if (source == QString("player/album")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song album"); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/salbum")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song album (%1 symbols)").arg(m_symbols); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/dalbum")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song album (%1 symbols, dynamic)") .arg(m_symbols); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/artist")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song artist"); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/sartist")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song artist (%1 symbols)").arg(m_symbols); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/dartist")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song artist (%1 symbols, dynamic)") .arg(m_symbols); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/duration")) { data[QString("min")] = 0; data[QString("max")] = 0; data[QString("name")] = QString("Current song duration"); data[QString("type")] = QString("integer"); data[QString("units")] = QString("s"); } else if (source == QString("player/progress")) { data[QString("min")] = 0; data[QString("max")] = 0; data[QString("name")] = QString("Current song progress"); data[QString("type")] = QString("integer"); data[QString("units")] = QString("s"); } else if (source == QString("player/title")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song title"); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/stitle")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song title (%1 symbols)").arg(m_symbols); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } else if (source == QString("player/dtitle")) { data[QString("min")] = QString(""); data[QString("max")] = QString(""); data[QString("name")] = QString("Current song title (%1 symbols, dynamic)") .arg(m_symbols); data[QString("type")] = QString("QString"); data[QString("units")] = QString(""); } return data; } void PlayerSource::run() { // initial data if (m_player == QString("mpd")) { // mpd m_values = getPlayerMpdInfo(m_mpdAddress); } else if (m_player == QString("mpris")) { // players which supports mpris if (m_dbusMutex.tryLock()) { QString mpris = m_mpris == QString("auto") ? getAutoMpris() : m_mpris; m_values = getPlayerMprisInfo(mpris); m_dbusMutex.unlock(); } } // dymanic properties // solid m_values[QString("player/salbum")] = stripString(m_values[QString("player/album")].toString(), m_symbols); m_values[QString("player/sartist")] = stripString(m_values[QString("player/artist")].toString(), m_symbols); m_values[QString("player/stitle")] = stripString(m_values[QString("player/title")].toString(), m_symbols); // dynamic m_values[QString("player/dalbum")] = buildString(m_values[QString("player/dalbum")].toString(), m_values[QString("player/album")].toString(), m_symbols); m_values[QString("player/dartist")] = buildString(m_values[QString("player/dartist")].toString(), m_values[QString("player/artist")].toString(), m_symbols); m_values[QString("player/dtitle")] = buildString(m_values[QString("player/dtitle")].toString(), m_values[QString("player/title")].toString(), m_symbols); } QStringList PlayerSource::sources() const { QStringList sources; sources.append(QString("player/album")); sources.append(QString("player/dalbum")); sources.append(QString("player/salbum")); sources.append(QString("player/artist")); sources.append(QString("player/dartist")); sources.append(QString("player/sartist")); sources.append(QString("player/duration")); sources.append(QString("player/progress")); sources.append(QString("player/title")); sources.append(QString("player/dtitle")); sources.append(QString("player/stitle")); return sources; } QString PlayerSource::buildString(const QString ¤t, 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(' ')); } void PlayerSource::updateMpdValue() { qCInfo(LOG_ESS) << "Cmd returns" << m_mpdProcess->exitCode(); QString qdebug = QTextCodec::codecForMib(106) ->toUnicode(m_mpdProcess->readAllStandardError()) .trimmed(); qCInfo(LOG_ESS) << "Error" << qdebug; QString qoutput = QTextCodec::codecForMib(106) ->toUnicode(m_mpdProcess->readAllStandardOutput()) .trimmed(); qCInfo(LOG_ESS) << "Output" << qoutput; for (auto str : qoutput.split(QChar('\n'), QString::SkipEmptyParts)) { if (str.split(QString(": "), QString::SkipEmptyParts).count() == 2) { // "Metadata: data" QString metadata = str.split(QString(": "), QString::SkipEmptyParts) .first() .toLower(); QString data = str.split(QString(": "), QString::SkipEmptyParts) .last() .trimmed(); // there are one more time... if ((metadata == QString("time")) && (data.contains(QChar(':')))) { QStringList times = data.split(QString(":")); m_mpdCached[QString("player/duration")] = times.at(0).toInt(); m_mpdCached[QString("player/progress")] = times.at(1).toInt(); } else if (m_metadata.contains(metadata)) { m_mpdCached[QString("player/%1").arg(metadata)] = data; } } } emit(dataReceived(m_mpdCached)); } QVariantHash PlayerSource::defaultInfo() const { QVariantHash info; info[QString("player/album")] = QString("unknown"); info[QString("player/artist")] = QString("unknown"); info[QString("player/duration")] = 0; info[QString("player/progress")] = 0; info[QString("player/title")] = QString("unknown"); return info; } QVariantHash PlayerSource::getPlayerMpdInfo(const QString mpdAddress) const { qCDebug(LOG_ESS) << "MPD" << mpdAddress; // build cmd QString cmd = QString("bash -c \"echo 'currentsong\nstatus\nclose' | curl " "--connect-timeout 1 -fsm 3 telnet://%1\"") .arg(mpdAddress); qCInfo(LOG_ESS) << "cmd" << cmd; m_mpdProcess->start(cmd); 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() << QString("org.mpris.MediaPlayer2.Player") << QString("Metadata"); QDBusMessage request = QDBusMessage::createMethodCall( QString("org.mpris.MediaPlayer2.%1").arg(mpris), QString("/org/mpris/MediaPlayer2"), QString(""), QString("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(response.arguments() .first() .value() .variant() .value()); info[QString("player/album")] = map.value(QString("xesam:album"), QString("unknown")); // artist is array info[QString("player/artist")] = map.value(QString("xesam:artist"), QString("unknown")).toString(); info[QString("player/duration")] = map.value(QString("mpris:length"), 0).toInt() / (1000 * 1000); info[QString("player/title")] = map.value(QString("xesam:title"), QString("unknown")); } // position args[1] = QString("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[QString("player/progress")] = response.arguments() .first() .value() .variant() .toLongLong() / (1000 * 1000); } return info; }