Refactor CMake files

This commit is contained in:
Jonas Kvinge
2024-10-03 22:32:51 +02:00
parent 525ebbb9b7
commit 0768298b95
43 changed files with 1494 additions and 1768 deletions

656
src/mpris2/mpris2.cpp Normal file
View File

@@ -0,0 +1,656 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <algorithm>
#include <utility>
#include <cmath>
#include <QApplication>
#include <QCoreApplication>
#include <QGuiApplication>
#include <QObject>
#include <QFile>
#include <QList>
#include <QVariant>
#include <QVariantMap>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusArgument>
#include <QDBusObjectPath>
#include "core/logging.h"
#include "mpris_common.h"
#include "mpris2.h"
#include "core/song.h"
#include "core/application.h"
#include "core/player.h"
#include "utilities/timeconstants.h"
#include "engine/enginebase.h"
#include "playlist/playlist.h"
#include "playlist/playlistitem.h"
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
#include "covermanager/currentalbumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
#include "mpris2_player.h"
#include "mpris2_playlists.h"
#include "mpris2_root.h"
#include "mpris2_tracklist.h"
using namespace Qt::StringLiterals;
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
arg.beginStructure();
arg << playlist.id << playlist.name << playlist.icon;
arg.endStructure();
return arg;
}
const QDBusArgument &operator>>(const QDBusArgument &arg, MprisPlaylist &playlist) {
arg.beginStructure();
arg >> playlist.id >> playlist.name >> playlist.icon;
arg.endStructure();
return arg;
}
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist) {
arg.beginStructure();
arg << playlist.valid;
arg << playlist.playlist;
arg.endStructure();
return arg;
}
const QDBusArgument &operator>>(const QDBusArgument &arg, MaybePlaylist &playlist) {
arg.beginStructure();
arg >> playlist.valid >> playlist.playlist;
arg.endStructure();
return arg;
}
namespace mpris {
constexpr char kMprisObjectPath[] = "/org/mpris/MediaPlayer2";
constexpr char kServiceName[] = "org.mpris.MediaPlayer2.strawberry";
constexpr char kFreedesktopPath[] = "org.freedesktop.DBus.Properties";
Mpris2::Mpris2(Application *app, QObject *parent)
: QObject(parent),
app_(app),
app_name_(QCoreApplication::applicationName()) {
new Mpris2Root(this);
new Mpris2TrackList(this);
new Mpris2Player(this);
new Mpris2Playlists(this);
if (!QDBusConnection::sessionBus().registerService(QLatin1String(kServiceName))) {
qLog(Warning) << "Failed to register" << kServiceName << "on the session bus";
return;
}
if (!QDBusConnection::sessionBus().registerObject(QLatin1String(kMprisObjectPath), this)) {
qLog(Warning) << "Failed to register" << kMprisObjectPath << "on the session bus";
return;
}
QObject::connect(&*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &Mpris2::AlbumCoverLoaded);
QObject::connect(&*app_->player()->engine(), &EngineBase::StateChanged, this, &Mpris2::EngineStateChanged);
QObject::connect(&*app_->player(), &Player::VolumeChanged, this, &Mpris2::VolumeChanged);
QObject::connect(&*app_->player(), &Player::Seeked, this, &Mpris2::Seeked);
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::PlaylistManagerInitialized, this, &Mpris2::PlaylistManagerInitialized);
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &Mpris2::CurrentSongChanged);
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot);
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged);
app_name_[0] = app_name_[0].toUpper();
QStringList data_dirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS")).split(u':');
if (!data_dirs.contains("/usr/local/share"_L1)) {
data_dirs.append(QStringLiteral("/usr/local/share"));
}
if (!data_dirs.contains("/usr/share"_L1)) {
data_dirs.append(QStringLiteral("/usr/share"));
}
for (const QString &data_dir : std::as_const(data_dirs)) {
const QString desktopfilepath = QStringLiteral("%1/applications/%2.desktop").arg(data_dir, QGuiApplication::desktopFileName());
if (QFile::exists(desktopfilepath)) {
desktopfilepath_ = desktopfilepath;
break;
}
}
if (desktopfilepath_.isEmpty()) {
desktopfilepath_ = QGuiApplication::desktopFileName() + QStringLiteral(".desktop");
}
}
// when PlaylistManager gets it ready, we connect PlaylistSequence with this
void Mpris2::PlaylistManagerInitialized() {
QObject::connect(app_->playlist_manager()->sequence(), &PlaylistSequence::ShuffleModeChanged, this, &Mpris2::ShuffleModeChanged);
QObject::connect(app_->playlist_manager()->sequence(), &PlaylistSequence::RepeatModeChanged, this, &Mpris2::RepeatModeChanged);
}
void Mpris2::EngineStateChanged(EngineBase::State newState) {
if (newState != EngineBase::State::Playing && newState != EngineBase::State::Paused) {
last_metadata_ = QVariantMap();
EmitNotification(QStringLiteral("Metadata"));
}
EmitNotification(QStringLiteral("CanPlay"));
EmitNotification(QStringLiteral("CanPause"));
EmitNotification(QStringLiteral("PlaybackStatus"), PlaybackStatus(newState));
if (newState == EngineBase::State::Playing) EmitNotification(QStringLiteral("CanSeek"), CanSeek(newState));
}
void Mpris2::VolumeChanged() {
EmitNotification(QStringLiteral("Volume"));
}
void Mpris2::ShuffleModeChanged() { EmitNotification(QStringLiteral("Shuffle")); }
void Mpris2::RepeatModeChanged() {
EmitNotification(QStringLiteral("LoopStatus"));
EmitNotification(QStringLiteral("CanGoNext"), CanGoNext());
EmitNotification(QStringLiteral("CanGoPrevious"), CanGoPrevious());
}
void Mpris2::EmitNotification(const QString &name, const QVariant &value) {
EmitNotification(name, value, QStringLiteral("org.mpris.MediaPlayer2.Player"));
}
void Mpris2::EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity) {
QDBusMessage msg = QDBusMessage::createSignal(QLatin1String(kMprisObjectPath), QLatin1String(kFreedesktopPath), QStringLiteral("PropertiesChanged"));
QVariantMap map;
map.insert(name, value);
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
msg.setArguments(args);
QDBusConnection::sessionBus().send(msg);
}
void Mpris2::EmitNotification(const QString &name) {
QVariant value;
if (name == "PlaybackStatus"_L1) value = PlaybackStatus();
else if (name == "LoopStatus"_L1) value = LoopStatus();
else if (name == "Shuffle"_L1) value = Shuffle();
else if (name == "Metadata"_L1) value = Metadata();
else if (name == "Rating"_L1) value = Rating();
else if (name == "Volume"_L1) value = Volume();
else if (name == "Position"_L1) value = Position();
else if (name == "CanPlay"_L1) value = CanPlay();
else if (name == "CanPause"_L1) value = CanPause();
else if (name == "CanSeek"_L1) value = CanSeek();
else if (name == "CanGoNext"_L1) value = CanGoNext();
else if (name == "CanGoPrevious"_L1) value = CanGoPrevious();
if (value.isValid()) EmitNotification(name, value);
}
//------------------Root Interface--------------------------//
bool Mpris2::CanQuit() const { return true; }
bool Mpris2::CanRaise() const { return true; }
bool Mpris2::HasTrackList() const { return true; }
QString Mpris2::Identity() const { return app_name_; }
QString Mpris2::DesktopEntryAbsolutePath() const {
return desktopfilepath_;
}
QString Mpris2::DesktopEntry() const { return QGuiApplication::desktopFileName(); }
QStringList Mpris2::SupportedUriSchemes() const {
static QStringList res = QStringList() << QStringLiteral("file")
<< QStringLiteral("http")
<< QStringLiteral("cdda")
<< QStringLiteral("smb")
<< QStringLiteral("sftp");
return res;
}
QStringList Mpris2::SupportedMimeTypes() const {
static QStringList res = QStringList() << QStringLiteral("x-content/audio-player")
<< QStringLiteral("application/ogg")
<< QStringLiteral("application/x-ogg")
<< QStringLiteral("application/x-ogm-audio")
<< QStringLiteral("audio/flac")
<< QStringLiteral("audio/ogg")
<< QStringLiteral("audio/vorbis")
<< QStringLiteral("audio/aac")
<< QStringLiteral("audio/mp4")
<< QStringLiteral("audio/mpeg")
<< QStringLiteral("audio/mpegurl")
<< QStringLiteral("audio/vnd.rn-realaudio")
<< QStringLiteral("audio/x-flac")
<< QStringLiteral("audio/x-oggflac")
<< QStringLiteral("audio/x-vorbis")
<< QStringLiteral("audio/x-vorbis+ogg")
<< QStringLiteral("audio/x-speex")
<< QStringLiteral("audio/x-wav")
<< QStringLiteral("audio/x-wavpack")
<< QStringLiteral("audio/x-ape")
<< QStringLiteral("audio/x-mp3")
<< QStringLiteral("audio/x-mpeg")
<< QStringLiteral("audio/x-mpegurl")
<< QStringLiteral("audio/x-ms-wma")
<< QStringLiteral("audio/x-musepack")
<< QStringLiteral("audio/x-pn-realaudio")
<< QStringLiteral("audio/x-scpls")
<< QStringLiteral("video/x-ms-asf");
return res;
}
void Mpris2::Raise() { Q_EMIT RaiseMainWindow(); }
void Mpris2::Quit() { QCoreApplication::quit(); }
QString Mpris2::PlaybackStatus() const {
return PlaybackStatus(app_->player()->GetState());
}
QString Mpris2::PlaybackStatus(EngineBase::State state) const {
switch (state) {
case EngineBase::State::Playing: return QStringLiteral("Playing");
case EngineBase::State::Paused: return QStringLiteral("Paused");
default: return QStringLiteral("Stopped");
}
}
QString Mpris2::LoopStatus() const {
if (!app_->playlist_manager()->sequence()) {
return QStringLiteral("None");
}
switch (app_->playlist_manager()->active() ? app_->playlist_manager()->active()->RepeatMode() : app_->playlist_manager()->sequence()->repeat_mode()) {
case PlaylistSequence::RepeatMode::Album:
case PlaylistSequence::RepeatMode::Playlist: return QStringLiteral("Playlist");
case PlaylistSequence::RepeatMode::Track: return QStringLiteral("Track");
default: return QStringLiteral("None");
}
}
void Mpris2::SetLoopStatus(const QString &value) {
PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off;
if (value == "None"_L1) {
mode = PlaylistSequence::RepeatMode::Off;
}
else if (value == "Track"_L1) {
mode = PlaylistSequence::RepeatMode::Track;
}
else if (value == "Playlist"_L1) {
mode = PlaylistSequence::RepeatMode::Playlist;
}
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
}
double Mpris2::Rate() const { return 1.0; }
void Mpris2::SetRate(double rate) {
if (rate == 0) {
app_->player()->Pause();
}
}
bool Mpris2::Shuffle() const {
const PlaylistSequence::ShuffleMode shuffle_mode = app_->playlist_manager()->active() ? app_->playlist_manager()->active()->ShuffleMode() : app_->playlist_manager()->sequence()->shuffle_mode();
return shuffle_mode != PlaylistSequence::ShuffleMode::Off;
}
void Mpris2::SetShuffle(bool enable) {
app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::ShuffleMode::All : PlaylistSequence::ShuffleMode::Off);
}
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
double Mpris2::Rating() const {
float rating = app_->playlist_manager()->active()->current_item_metadata().rating();
return (rating <= 0) ? 0 : rating;
}
void Mpris2::SetRating(double rating) {
if (rating > 1.0) {
rating = 1.0;
}
else if (rating <= 0.0) {
rating = -1.0;
}
app_->playlist_manager()->RateCurrentSong(static_cast<float>(rating));
}
QDBusObjectPath Mpris2::current_track_id() const {
return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row())));
}
// We send Metadata change notification as soon as the process of changing song starts...
void Mpris2::CurrentSongChanged(const Song &song) {
AlbumCoverLoaded(song);
EmitNotification(QStringLiteral("CanPlay"));
EmitNotification(QStringLiteral("CanPause"));
EmitNotification(QStringLiteral("CanGoNext"), CanGoNext());
EmitNotification(QStringLiteral("CanGoPrevious"), CanGoPrevious());
EmitNotification(QStringLiteral("CanSeek"), CanSeek());
}
// ... and we add the cover information later, when it's available.
void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
last_metadata_ = QVariantMap();
song.ToXesam(&last_metadata_);
using mpris::AddMetadata;
AddMetadata(QStringLiteral("mpris:trackid"), current_track_id(), &last_metadata_);
QUrl cover_url;
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
cover_url = result.album_cover.cover_url;
}
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
cover_url = result.temp_cover_url;
}
else if (song.art_manual().isValid() && song.art_manual().isLocalFile()) {
cover_url = song.art_manual();
}
else if (song.art_automatic().isValid() && song.art_automatic().isLocalFile()) {
cover_url = song.art_automatic();
}
if (cover_url.isValid()) {
AddMetadata(QStringLiteral("mpris:artUrl"), cover_url.toString(), &last_metadata_);
}
AddMetadata(QStringLiteral("year"), song.year(), &last_metadata_);
AddMetadata(QStringLiteral("bitrate"), song.bitrate(), &last_metadata_);
EmitNotification(QStringLiteral("Metadata"), last_metadata_);
}
double Mpris2::Volume() const {
return app_->player()->GetVolume() / 100.0;
}
void Mpris2::SetVolume(const double volume) {
app_->player()->SetVolume(static_cast<uint>(qBound(0L, lround(volume * 100.0), 100L)));
}
qint64 Mpris2::Position() const {
return app_->player()->engine()->position_nanosec() / kNsecPerUsec;
}
double Mpris2::MaximumRate() const { return 1.0; }
double Mpris2::MinimumRate() const { return 1.0; }
bool Mpris2::CanGoNext() const {
return app_->playlist_manager()->active() && app_->playlist_manager()->active()->next_row() != -1;
}
bool Mpris2::CanGoPrevious() const {
return app_->playlist_manager()->active() && (app_->playlist_manager()->active()->previous_row() != -1 || app_->player()->PreviousWouldRestartTrack());
}
bool Mpris2::CanPlay() const {
return app_->playlist_manager()->active() && app_->playlist_manager()->active()->rowCount() != 0;
}
// This one's a bit different than MPRIS 1 - we want this to be true even when the song is already paused or stopped.
bool Mpris2::CanPause() const {
return (app_->player()->GetCurrentItem() && app_->player()->GetState() == EngineBase::State::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::Option::PauseDisabled)) || PlaybackStatus() == "Paused"_L1 || PlaybackStatus() == "Stopped"_L1;
}
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
bool Mpris2::CanSeek(EngineBase::State state) const {
return app_->player()->GetCurrentItem() && state != EngineBase::State::Empty && !app_->player()->GetCurrentItem()->Metadata().is_stream();
}
bool Mpris2::CanControl() const { return true; }
void Mpris2::Next() {
if (CanGoNext()) {
app_->player()->Next();
}
}
void Mpris2::Previous() {
if (CanGoPrevious()) {
app_->player()->Previous();
}
}
void Mpris2::Pause() {
if (CanPause() && app_->player()->GetState() != EngineBase::State::Paused) {
app_->player()->Pause();
}
}
void Mpris2::PlayPause() {
if (CanPause()) {
app_->player()->PlayPause();
}
}
void Mpris2::Stop() { app_->player()->Stop(); }
void Mpris2::Play() {
if (CanPlay()) {
app_->player()->Play();
}
}
void Mpris2::Seek(qint64 offset) {
if (CanSeek()) {
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + offset / kUsecPerSec);
}
}
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
if (CanSeek() && trackId == current_track_id() && offset >= 0) {
offset *= kNsecPerUsec;
if (offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) {
app_->player()->SeekTo(offset / kNsecPerSec);
}
}
}
void Mpris2::OpenUri(const QString &uri) {
app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(uri), -1, true);
}
Track_Ids Mpris2::Tracks() const {
// TODO
return Track_Ids();
}
bool Mpris2::CanEditTracks() const { return false; }
TrackMetadata Mpris2::GetTracksMetadata(const Track_Ids &tracks) const {
Q_UNUSED(tracks);
// TODO
return TrackMetadata();
}
void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) {
Q_UNUSED(uri);
Q_UNUSED(afterTrack);
Q_UNUSED(setAsCurrent);
// TODO
}
void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) {
Q_UNUSED(trackId);
// TODO
}
void Mpris2::GoTo(const QDBusObjectPath &trackId) {
Q_UNUSED(trackId);
// TODO
}
quint32 Mpris2::PlaylistCount() const {
return app_->playlist_manager()->GetAllPlaylists().size();
}
QStringList Mpris2::Orderings() const { return QStringList() << QStringLiteral("User"); }
namespace {
QDBusObjectPath MakePlaylistPath(int id) {
return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/PlaylistId/%1").arg(id));
}
} // namespace
MaybePlaylist Mpris2::ActivePlaylist() const {
MaybePlaylist maybe_playlist;
Playlist *current_playlist = app_->playlist_manager()->current();
maybe_playlist.valid = current_playlist;
if (!current_playlist) {
return maybe_playlist;
}
maybe_playlist.playlist.id = MakePlaylistPath(current_playlist->id());
maybe_playlist.playlist.name = app_->playlist_manager()->GetPlaylistName(current_playlist->id());
return maybe_playlist;
}
void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) {
QStringList split_path = playlist_id.path().split(u'/');
qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path;
if (split_path.isEmpty()) {
return;
}
bool ok = false;
int p = split_path.last().toInt(&ok);
if (!ok) {
return;
}
if (!app_->playlist_manager()->IsPlaylistOpen(p)) {
qLog(Error) << "Playlist isn't opened!";
return;
}
app_->playlist_manager()->SetActivePlaylist(p);
app_->player()->Next();
}
// TODO: Support sort orders.
MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order) {
Q_UNUSED(order);
const QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
MprisPlaylistList ret;
ret.reserve(playlists.count());
for (Playlist *p : playlists) {
MprisPlaylist mpris_playlist;
mpris_playlist.id = MakePlaylistPath(p->id());
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id());
ret << mpris_playlist;
}
if (reverse_order) {
std::reverse(ret.begin(), ret.end());
}
return ret.mid(index, max_count);
}
void Mpris2::PlaylistChangedSlot(Playlist *playlist) {
MprisPlaylist mpris_playlist;
mpris_playlist.id = MakePlaylistPath(playlist->id());
mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id());
Q_EMIT PlaylistChanged(mpris_playlist);
}
void Mpris2::PlaylistCollectionChanged(Playlist *playlist) {
Q_UNUSED(playlist);
EmitNotification(QStringLiteral("PlaylistCount"), ""_L1, QStringLiteral("org.mpris.MediaPlayer2.Playlists"));
}
} // namespace mpris

245
src/mpris2/mpris2.h Normal file
View File

@@ -0,0 +1,245 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MPRIS2_H
#define MPRIS2_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QMap>
#include <QSet>
#include <QMetaType>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QDBusObjectPath>
#include <QDBusArgument>
#include <QJsonObject>
#include "engine/enginebase.h"
#include "covermanager/albumcoverloaderresult.h"
class Application;
class Song;
class Playlist;
using TrackMetadata = QList<QVariantMap>;
using Track_Ids = QList<QDBusObjectPath>;
Q_DECLARE_METATYPE(TrackMetadata)
struct MprisPlaylist {
QDBusObjectPath id;
QString name;
QString icon; // Uri
};
using MprisPlaylistList = QList<MprisPlaylist>;
Q_DECLARE_METATYPE(MprisPlaylist)
Q_DECLARE_METATYPE(MprisPlaylistList)
struct MaybePlaylist {
bool valid;
MprisPlaylist playlist;
};
Q_DECLARE_METATYPE(MaybePlaylist)
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist);
const QDBusArgument &operator>>(const QDBusArgument &arg, MprisPlaylist &playlist);
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist);
const QDBusArgument &operator>>(const QDBusArgument &arg, MaybePlaylist &playlist);
namespace mpris {
class Mpris2 : public QObject {
Q_OBJECT
public:
explicit Mpris2(Application *app, QObject *parent = nullptr);
// org.mpris.MediaPlayer2 MPRIS 2.0 Root interface
Q_PROPERTY(bool CanQuit READ CanQuit)
Q_PROPERTY(bool CanRaise READ CanRaise)
Q_PROPERTY(bool HasTrackList READ HasTrackList)
Q_PROPERTY(QString Identity READ Identity)
Q_PROPERTY(QString DesktopEntry READ DesktopEntry)
Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes)
Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes)
// org.mpris.MediaPlayer2 MPRIS 2.2 Root interface
Q_PROPERTY(bool CanSetFullscreen READ CanSetFullscreen)
Q_PROPERTY(bool Fullscreen READ Fullscreen WRITE SetFullscreen)
// org.mpris.MediaPlayer2.Player MPRIS 2.0 Player interface
Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus)
Q_PROPERTY(QString LoopStatus READ LoopStatus WRITE SetLoopStatus)
Q_PROPERTY(double Rate READ Rate WRITE SetRate)
Q_PROPERTY(bool Shuffle READ Shuffle WRITE SetShuffle)
Q_PROPERTY(QVariantMap Metadata READ Metadata)
Q_PROPERTY(double Volume READ Volume WRITE SetVolume)
Q_PROPERTY(qint64 Position READ Position)
Q_PROPERTY(double MinimumRate READ MinimumRate)
Q_PROPERTY(double MaximumRate READ MaximumRate)
Q_PROPERTY(bool CanGoNext READ CanGoNext)
Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious)
Q_PROPERTY(bool CanPlay READ CanPlay)
Q_PROPERTY(bool CanPause READ CanPause)
Q_PROPERTY(bool CanSeek READ CanSeek)
Q_PROPERTY(bool CanControl READ CanControl)
// org.mpris.MediaPlayer2.TrackList MPRIS 2.0 Player interface
Q_PROPERTY(Track_Ids Tracks READ Tracks)
Q_PROPERTY(bool CanEditTracks READ CanEditTracks)
// org.mpris.MediaPlayer2.Playlists MPRIS 2.1 Playlists interface
Q_PROPERTY(quint32 PlaylistCount READ PlaylistCount)
Q_PROPERTY(QStringList Orderings READ Orderings)
Q_PROPERTY(MaybePlaylist ActivePlaylist READ ActivePlaylist)
// strawberry specific additional property to extend MPRIS Player interface
Q_PROPERTY(double Rating READ Rating WRITE SetRating)
// Root Properties
bool CanQuit() const;
bool CanRaise() const;
bool HasTrackList() const;
QString Identity() const;
QString DesktopEntry() const;
QStringList SupportedUriSchemes() const;
QStringList SupportedMimeTypes() const;
// Root Properties added in MPRIS 2.2
bool CanSetFullscreen() const { return false; }
bool Fullscreen() const { return false; }
void SetFullscreen(bool) {}
// Methods
void Raise();
void Quit();
// Player Properties
QString PlaybackStatus() const;
QString LoopStatus() const;
void SetLoopStatus(const QString &value);
double Rate() const;
void SetRate(double rate);
bool Shuffle() const;
void SetShuffle(bool enable);
QVariantMap Metadata() const;
double Rating() const;
void SetRating(double rating);
double Volume() const;
void SetVolume(const double volume);
qint64 Position() const;
double MaximumRate() const;
double MinimumRate() const;
bool CanGoNext() const;
bool CanGoPrevious() const;
bool CanPlay() const;
bool CanPause() const;
bool CanSeek() const;
bool CanControl() const;
// Methods
void Next();
void Previous();
void Pause();
void PlayPause();
void Stop();
void Play();
void Seek(qint64 offset);
void SetPosition(const QDBusObjectPath &trackId, qint64 offset);
void OpenUri(const QString &uri);
// TrackList Properties
Track_Ids Tracks() const;
bool CanEditTracks() const;
// Methods
TrackMetadata GetTracksMetadata(const Track_Ids &tracks) const;
void AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent);
void RemoveTrack(const QDBusObjectPath &trackId);
void GoTo(const QDBusObjectPath &trackId);
// Playlist Properties
quint32 PlaylistCount() const;
QStringList Orderings() const;
MaybePlaylist ActivePlaylist() const;
// Methods
void ActivatePlaylist(const QDBusObjectPath &playlist_id);
MprisPlaylistList GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order);
Q_SIGNALS:
// Player
void Seeked(const qint64 position);
// TrackList
void TrackListReplaced(const Track_Ids &tracks, const QDBusObjectPath &current_track);
void TrackAdded(const TrackMetadata &metadata, const QDBusObjectPath &after_track);
void TrackRemoved(const QDBusObjectPath &track_id);
void TrackMetadataChanged(const QDBusObjectPath &track_id, const TrackMetadata &metadata);
void RaiseMainWindow();
// Playlist
void PlaylistChanged(const MprisPlaylist &playlist);
private Q_SLOTS:
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult());
void EngineStateChanged(EngineBase::State newState);
void VolumeChanged();
void PlaylistManagerInitialized();
void CurrentSongChanged(const Song &song);
void ShuffleModeChanged();
void RepeatModeChanged();
void PlaylistChangedSlot(Playlist *playlist);
void PlaylistCollectionChanged(Playlist *playlist);
private:
void EmitNotification(const QString &name);
void EmitNotification(const QString &name, const QVariant &value);
void EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity);
QString PlaybackStatus(EngineBase::State state) const;
QDBusObjectPath current_track_id() const;
bool CanSeek(EngineBase::State state) const;
QString DesktopEntryAbsolutePath() const;
private:
Application *app_;
QString app_name_;
QString desktopfilepath_;
QVariantMap last_metadata_;
};
} // namespace mpris
#endif // MPRIS2_H

71
src/mpris2/mpris_common.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef MPRIS_COMMON_H
#define MPRIS_COMMON_H
#include "config.h"
#include <QObject>
#include <QVariantMap>
#include <QDateTime>
#include <QString>
#include <QStringList>
#include <QDBusObjectPath>
namespace mpris {
using namespace Qt::Literals::StringLiterals;
inline void AddMetadata(const QString &key, const QString &metadata, QVariantMap *map) {
if (!metadata.isEmpty()) (*map)[key] = metadata;
}
inline void AddMetadataAsList(const QString &key, const QString &metadata, QVariantMap *map) {
if (!metadata.isEmpty()) (*map)[key] = QStringList() << metadata;
}
inline void AddMetadata(const QString &key, int metadata, QVariantMap *map) {
if (metadata > 0) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, qint64 metadata, QVariantMap *map) {
if (metadata > 0) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, double metadata, QVariantMap *map) {
if (metadata != 0.0) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, const QDateTime &metadata, QVariantMap *map) {
if (metadata.isValid()) (*map)[key] = metadata;
}
inline void AddMetadata(const QString &key, const QDBusObjectPath &metadata, QVariantMap *map) {
if (metadata.path().length() > 0) (*map)[key] = QVariant::fromValue<QDBusObjectPath>(metadata);
}
inline QString AsMPRISDateTimeType(const qint64 time) {
return time != -1 ? QDateTime::fromSecsSinceEpoch(time).toString(Qt::ISODate) : ""_L1;
}
} // namespace mpris
#endif // MPRIS_COMMON_H

View File

@@ -0,0 +1,44 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2.Player'>
<method name='Next'/>
<method name='Previous'/>
<method name='Pause'/>
<method name='PlayPause'/>
<method name='Stop'/>
<method name='Play'/>
<method name='Seek'>
<arg direction='in' name='Offset' type='x'/>
</method>
<method name='SetPosition'>
<arg direction='in' name='TrackId' type='o'/>
<arg direction='in' name='Position' type='x'/>
</method>
<method name='OpenUri'>
<arg direction='in' name='Uri' type='s'/>
</method>
<signal name='Seeked'>
<arg name='Position' type='x'/>
</signal>
<property name='PlaybackStatus' type='s' access='read'/>
<property name='LoopStatus' type='s' access='readwrite'/>
<property name='Rate' type='d' access='readwrite'/>
<property name='Shuffle' type='b' access='readwrite'/>
<property name='Metadata' type='a{sv}' access='read'>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
</property>
<property name='Rating' type='d' access='readwrite'/>
<property name='Volume' type='d' access='readwrite'/>
<property name='Position' type='x' access='read'/>
<property name='MinimumRate' type='d' access='read'/>
<property name='MaximumRate' type='d' access='read'/>
<property name='CanGoNext' type='b' access='read'/>
<property name='CanGoPrevious' type='b' access='read'/>
<property name='CanPlay' type='b' access='read'/>
<property name='CanPause' type='b' access='read'/>
<property name='CanSeek' type='b' access='read'/>
<property name='CanControl' type='b' access='read'/>
</interface>
</node>

View File

@@ -0,0 +1,28 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2.Playlists'>
<property name='PlaylistCount' type='u' access='read' />
<property name='Orderings' type='as' access='read' />
<property name='ActivePlaylist' type='(b(oss))' access='read'>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="MaybePlaylist" />
</property>
<method name='ActivatePlaylist'>
<arg direction='in' name='PlaylistId' type='o' />
</method>
<method name='GetPlaylists'>
<arg direction='in' name='Index' type='u' />
<arg direction='in' name='MaxCount' type='u' />
<arg direction='in' name='Order' type='s' />
<arg direction='in' name='ReverseOrder' type='b' />
<arg direction='out' name='Playlists' type='a(oss)' />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MprisPlaylistList" />
</method>
<signal name='PlaylistChanged'>
<arg direction='out' name='Playlist' type='(oss)' />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MprisPlaylist" />
</signal>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="MaybePlaylist" />
</interface>
</node>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2.TrackList'>
<method name='GetTracksMetadata'>
<arg direction='in' name='TrackIds' type='ao' />
<arg direction='out' name='Metadata' type='aa{sv}' />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="TrackMetadata" />
</method>
<method name='AddTrack'>
<arg direction='in' name='Uri' type='s' />
<arg direction='in' name='AfterTrack' type='o' />
<arg direction='in' name='SetAsCurrent' type='b' />
</method>
<method name='RemoveTrack'>
<arg direction='in' name='TrackId' type='o' />
</method>
<method name='GoTo'>
<arg direction='in' name='TrackId' type='o' />
</method>
<signal name='TrackListReplaced'>
<arg name='Tracks' type='ao' />
<arg name='CurrentTrack' type='o' />
</signal>
<signal name='TrackAdded'>
<arg direction='out' name='Metadata' type='a{sv}' />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="TrackMetadata" />
<arg name='AfterTrack' type='o' />
</signal>
<signal name='TrackRemoved'>
<arg name='TrackId' type='o' />
</signal>
<signal name='TrackMetadataChanged'>
<arg name='TrackId' type='o' />
<arg direction='out' name='Metadata' type='a{sv}' />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="TrackMetadata" />
</signal>
<property name='Tracks' type='ao' access='read' />
<property name='CanEditTracks' type='b' access='read' />
</interface>
</node>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name='org.mpris.MediaPlayer2'>
<method name='Raise' />
<method name='Quit' />
<property name='CanQuit' type='b' access='read' />
<property name='CanRaise' type='b' access='read' />
<property name='HasTrackList' type='b' access='read' />
<property name='Identity' type='s' access='read' />
<property name='DesktopEntry' type='s' access='read' />
<property name='SupportedUriSchemes' type='as' access='read' />
<property name='SupportedMimeTypes' type='as' access='read' />
<property name='Fullscreen' type='b' access='readwrite' />
<property name='CanSetFullscreen' type='b' access='read' />
</interface>
</node>