/* * Copyright (C) 2008 Bruno Virlet * Copyright (C) 2009 Ryan P. Bitanga * Copyright (C) 2009 Jan G. Marker * Copyright (C) 2021 Ivailo Monev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "audioplayercontrolrunner.h" #include "audioplayercontrolconfigkeys.h" #include #include #include #include #include #include #include #include #include #include #include #include // for reference: // https://specifications.freedesktop.org/mpris-spec/latest/ AudioPlayerControlRunner::AudioPlayerControlRunner(QObject *parent, const QVariantList& args) : Plasma::AbstractRunner(parent, args) { Q_UNUSED(args); setObjectName(QLatin1String("Audio Player Control Runner")); setSpeed(AbstractRunner::SlowSpeed); connect(this, SIGNAL(prepare()), this, SLOT(prep())); reloadConfiguration(); } AudioPlayerControlRunner::~AudioPlayerControlRunner() { } void AudioPlayerControlRunner::prep() { m_running = false; m_canPlay = false; m_canGoNext = false; m_canGoPrevious = false; m_playbackStatus = QString(); m_identity = m_player; QDBusInterface player(QString::fromLatin1("org.mpris.MediaPlayer2.%1").arg(m_player), QLatin1String("/org/mpris/MediaPlayer2"), QLatin1String("org.mpris.MediaPlayer2.Player")); m_running = player.isValid(); if (m_running) { QDBusInterface mediaplayer2(QString::fromLatin1("org.mpris.MediaPlayer2.%1").arg(m_player), QLatin1String("/org/mpris/MediaPlayer2"), QLatin1String("org.mpris.MediaPlayer2")); m_identity = mediaplayer2.property("Identity").toString(); m_playbackStatus = player.property("PlaybackStatus").toString(); // NOTE: Bogus values set by Audacious for the properties bellow m_canPlay = player.property("CanPlay").toBool(); m_canGoNext = player.property("CanGoNext").toBool(); m_canGoPrevious = player.property("CanGoPrevious").toBool(); } // qDebug() << Q_FUNC_INFO << m_running << m_canPlay << m_canGoNext << m_canGoPrevious << m_playbackStatus << m_identity; } void AudioPlayerControlRunner::match(Plasma::RunnerContext &context) { if (context.query().length() < 3) { return; } const QString term = context.query(); QList matches; if (context.isValid() ) { QVariantList playcontrol; playcontrol << QLatin1String("/org/mpris/MediaPlayer2") << QLatin1String("org.mpris.MediaPlayer2.Player"); // Play if (m_comPlay.startsWith(term, Qt::CaseInsensitive) && (!m_running || (m_canPlay && m_playbackStatus != QLatin1String("Playing")))) { QVariantList data = playcontrol; data << QLatin1String("Play") << QLatin1String("start"); matches << createMatch(this, i18n("Start playing"), i18n("Audio player control"), QLatin1String("play"), KIcon(QLatin1String("media-playback-start")), data, 1.0); } if (!m_running) { // The interface of the player is not availalbe, so the rest of the commands are not needed context.addMatches(term, matches); return; } // Next song if (m_comNext.startsWith(term, Qt::CaseInsensitive) && m_canGoNext) { QVariantList data = playcontrol; data << QLatin1String("Next") << QLatin1String("nostart"); matches << createMatch(this, i18n("Play next song"), i18n("Audio player control"), QLatin1String("next"), KIcon(QLatin1String("media-skip-forward")), data, 1.0); } // Previous song if (m_comPrev.startsWith(term, Qt::CaseInsensitive) && m_canGoPrevious) { QVariantList data = playcontrol; data << QLatin1String("Previous") << QLatin1String("nostart"); matches << createMatch(this, i18n("Play previous song"), i18n("Audio player control") , QLatin1String("previous"), KIcon(QLatin1String("media-skip-backward")), data, 1.0); } // Pause if (m_comPause.startsWith(term, Qt::CaseInsensitive) && m_playbackStatus == QLatin1String("Playing")) { QVariantList data = playcontrol; data << QLatin1String("Pause") << QLatin1String("nostart"); matches << createMatch(this, i18n("Pause playing"), i18n("Audio player control"), QLatin1String("pause"), KIcon(QLatin1String("media-playback-pause")), data, 1.0); } // Stop if (m_comStop.startsWith(term, Qt::CaseInsensitive) && m_playbackStatus == QLatin1String("Playing")) { QVariantList data = playcontrol; data << QLatin1String("Stop") << QLatin1String("nostart"); matches << createMatch(this, i18n("Stop playing"), i18n("Audio player control"), QLatin1String("stop"), KIcon(QLatin1String("media-playback-stop")), data, 1.0); } // Set volume to QRegExp volumeRegxp(m_comVolume + QLatin1String(" \\d{1,2}0{0,1}")); volumeRegxp.setCaseSensitivity(Qt::CaseInsensitive); if (volumeRegxp.exactMatch(term)) { QVariantList data = playcontrol; int newVolume = getNumber(term , ' '); data << QLatin1String("Volume") << QLatin1String("nostart") << (newVolume / 100.0); matches << createMatch(this, i18n("Set volume to %1%" , newVolume), QLatin1String("volume"), i18n("Audio player control"), KIcon(QLatin1String("audio-volume-medium")), data, 1.0); } // Quit player if (m_comQuit.startsWith(term, Qt::CaseInsensitive)) { QVariantList data; data << QLatin1String("/org/mpris/MediaPlayer2") << QLatin1String("org.mpris.MediaPlayer2") << QLatin1String("Quit") << QLatin1String("nostart"); matches << createMatch(this, i18n("Quit %1", m_identity), QLatin1String(""), QLatin1String("quit"), KIcon(QLatin1String("application-exit")), data, 1.0); } } context.addMatches(term, matches); } void AudioPlayerControlRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context) QVariantList data = match.data().value(); if (data[3].toString().compare(QLatin1String("start")) == 0) { // The players's interface isn't available but it should be started if (!startPlayer()) { return; } } // Special case for properties if (data[2].toString().compare(QLatin1String("Volume")) == 0) { QDBusInterface player(QString::fromLatin1("org.mpris.MediaPlayer2.%1").arg(m_player), data[0].toString(), data[1].toString()); player.setProperty(data[2].toByteArray(), data[4]); return; } QDBusMessage msg = QDBusMessage::createMethodCall(QString::fromLatin1("org.mpris.MediaPlayer2.%1").arg(m_player), data[0].toString(), data[1].toString(), data[2].toString()); kDebug() << msg; QVariantList args; for (int i = 4; data.length() > i;++i) { args << data[i]; } msg.setArguments(args); QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); } void AudioPlayerControlRunner::reloadConfiguration() { KConfigGroup grp = config(); m_player = grp.readEntry(CONFIG_PLAYER, "vlc"); m_comPlay = grp.readEntry(CONFIG_PLAY, i18n("play")); m_comPause = grp.readEntry(CONFIG_PAUSE, i18n("pause")); m_comNext = grp.readEntry(CONFIG_NEXT, i18n("next")); m_comPrev = grp.readEntry(CONFIG_PREV, i18n("prev")); m_comStop = grp.readEntry(CONFIG_STOP, i18n("stop")); m_comVolume = grp.readEntry(CONFIG_VOLUME, i18n("volume")); m_comQuit = grp.readEntry(CONFIG_QUIT, i18n("quit")); // Adding the syntaxes for helping the user QList syntaxes; syntaxes << Plasma::RunnerSyntax(m_comPlay, i18n("Plays a song from playlist")); syntaxes << Plasma::RunnerSyntax(m_comPause,i18n("Pauses the playing")); syntaxes << Plasma::RunnerSyntax(m_comNext, i18n("Plays the next song in the playlist if one is available")); syntaxes << Plasma::RunnerSyntax(m_comPrev, i18n("Plays the previous song if one is available")); syntaxes << Plasma::RunnerSyntax(m_comStop, i18n("Stops the playing")); syntaxes << Plasma::RunnerSyntax(m_comVolume + QLatin1String(" :q:"), i18n("Sets the volume to :q:")); syntaxes << Plasma::RunnerSyntax(m_comQuit, i18n("Quits the player")); setSyntaxes(syntaxes); } Plasma::QueryMatch AudioPlayerControlRunner::createMatch(Plasma::AbstractRunner* runner, const QString &title, const QString &subtext, const QString &id, const KIcon &icon, const QVariantList &data, const float &relevance) { Plasma::QueryMatch match(runner); match.setText(title); match.setSubtext(subtext); match.setId(id); match.setIcon(icon); match.setData(data); match.setRelevance(relevance); return match; } bool AudioPlayerControlRunner::startPlayer() { if (m_running) { return true; } if (!KRun::run(m_player, KUrl::List(), nullptr)) { // We couldn't start the player KMessageBox::error(nullptr, i18n("%1 not found", m_player), i18n("%1 was not found so the runner is unable to work.", m_player)); return false; } QElapsedTimer waitTimeout; waitTimeout.start(); while (!m_running && waitTimeout.elapsed() < 30000) { // Waiting for the player's interface to appear QDBusInterface player(QString::fromLatin1("org.mpris.MediaPlayer2.%1").arg(m_player), QLatin1String("/org/mpris/MediaPlayer2"), QLatin1String("org.mpris.MediaPlayer2.Player")); m_running = player.isValid(); QCoreApplication::processEvents(); } if (m_running) { prep(); return true; } return false; } int AudioPlayerControlRunner::getNumber(const QString& term, const char character) { return term.section(QLatin1Char(character), 1, 1).toInt(); } #include "moc_audioplayercontrolrunner.cpp"