forked from quickshell/quickshell
service/mpris: adopt bindable properties
This commit is contained in:
parent
1955deee74
commit
db9e633197
3 changed files with 263 additions and 250 deletions
|
@ -4,15 +4,16 @@
|
|||
#include <qdatetime.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qstring.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/util.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "dbus_player.h"
|
||||
#include "dbus_player_app.h"
|
||||
|
@ -57,37 +58,81 @@ MprisPlayer::MprisPlayer(const QString& address, QObject* parent): QObject(paren
|
|||
return;
|
||||
}
|
||||
|
||||
this->bCanPlay.setBinding([this]() { return this->bCanControl && this->bpCanPlay; });
|
||||
this->bCanPause.setBinding([this]() { return this->bCanControl && this->bpCanPause; });
|
||||
this->bCanSeek.setBinding([this]() { return this->bCanControl && this->bpCanSeek; });
|
||||
this->bCanGoNext.setBinding([this]() { return this->bCanControl && this->bpCanGoNext; });
|
||||
this->bCanGoPrevious.setBinding([this]() { return this->bCanControl && this->bpCanGoPrevious; });
|
||||
|
||||
this->bCanTogglePlaying.setBinding([this]() {
|
||||
return this->bPlaybackState == MprisPlaybackState::Playing ? this->bCanPause.value()
|
||||
: this->bCanPlay.value();
|
||||
});
|
||||
|
||||
this->bTrackTitle.setBinding([this]() {
|
||||
const auto& title = this->bMetadata.value().value("xesam:title").toString();
|
||||
return title.isNull() ? QStringLiteral("Unknown Track") : title;
|
||||
});
|
||||
|
||||
this->bTrackAlbum.setBinding([this]() {
|
||||
const auto& album = this->bMetadata.value().value("xesam:album").toString();
|
||||
return album.isNull() ? QStringLiteral("Unknown Album") : album;
|
||||
});
|
||||
|
||||
this->bTrackArtist.setBinding([this]() {
|
||||
const auto& artist = this->bMetadata.value().value("xesam:artist").value<QList<QString>>();
|
||||
return artist.isEmpty() ? QStringLiteral("Unknown Artist") : artist.join(", ");
|
||||
});
|
||||
|
||||
this->bTrackAlbumArtist.setBinding([this]() {
|
||||
const auto& artist = this->bMetadata.value().value("xesam:albumArtist").toString();
|
||||
return artist.isNull() ? QStringLiteral("Unknown Artist") : artist;
|
||||
});
|
||||
|
||||
this->bTrackArtUrl.setBinding([this]() {
|
||||
return this->bMetadata.value().value("mpris:artUrl").toString();
|
||||
});
|
||||
|
||||
this->bInternalLength.setBinding([this]() {
|
||||
auto variant = this->bMetadata.value().value("mpris:length");
|
||||
if (variant.isValid() && variant.canConvert<qlonglong>()) {
|
||||
return variant.value<qlonglong>();
|
||||
} else return static_cast<qlonglong>(-1);
|
||||
});
|
||||
|
||||
this->bPlaybackState.setBinding([this]() {
|
||||
const auto& status = this->bpPlaybackStatus.value();
|
||||
|
||||
if (status == "Playing") {
|
||||
return MprisPlaybackState::Playing;
|
||||
} else if (status == "Paused") {
|
||||
this->pausedTime = QDateTime::currentDateTimeUtc();
|
||||
return MprisPlaybackState::Paused;
|
||||
} else if (status == "Stopped") {
|
||||
return MprisPlaybackState::Stopped;
|
||||
} else {
|
||||
qWarning() << "Received unexpected PlaybackStatus for" << this << status;
|
||||
return MprisPlaybackState::Stopped;
|
||||
}
|
||||
});
|
||||
|
||||
this->bLoopState.setBinding([this]() {
|
||||
const auto& status = this->bpLoopStatus.value();
|
||||
|
||||
if (status == "None") {
|
||||
return MprisLoopState::None;
|
||||
} else if (status == "Track") {
|
||||
return MprisLoopState::Track;
|
||||
} else if (status == "Playlist") {
|
||||
return MprisLoopState::Playlist;
|
||||
} else {
|
||||
qWarning() << "Received unexpected LoopStatus for" << this << status;
|
||||
return MprisLoopState::None;
|
||||
}
|
||||
});
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(&this->pCanQuit, &AbstractDBusProperty::changed, this, &MprisPlayer::canQuitChanged);
|
||||
QObject::connect(&this->pCanRaise, &AbstractDBusProperty::changed, this, &MprisPlayer::canRaiseChanged);
|
||||
QObject::connect(&this->pCanSetFullscreen, &AbstractDBusProperty::changed, this, &MprisPlayer::canSetFullscreenChanged);
|
||||
QObject::connect(&this->pIdentity, &AbstractDBusProperty::changed, this, &MprisPlayer::identityChanged);
|
||||
QObject::connect(&this->pDesktopEntry, &AbstractDBusProperty::changed, this, &MprisPlayer::desktopEntryChanged);
|
||||
QObject::connect(&this->pFullscreen, &AbstractDBusProperty::changed, this, &MprisPlayer::fullscreenChanged);
|
||||
QObject::connect(&this->pSupportedUriSchemes, &AbstractDBusProperty::changed, this, &MprisPlayer::supportedUriSchemesChanged);
|
||||
QObject::connect(&this->pSupportedMimeTypes, &AbstractDBusProperty::changed, this, &MprisPlayer::supportedMimeTypesChanged);
|
||||
|
||||
QObject::connect(&this->pCanControl, &AbstractDBusProperty::changed, this, &MprisPlayer::canControlChanged);
|
||||
QObject::connect(&this->pCanSeek, &AbstractDBusProperty::changed, this, &MprisPlayer::canSeekChanged);
|
||||
QObject::connect(&this->pCanGoNext, &AbstractDBusProperty::changed, this, &MprisPlayer::canGoNextChanged);
|
||||
QObject::connect(&this->pCanGoPrevious, &AbstractDBusProperty::changed, this, &MprisPlayer::canGoPreviousChanged);
|
||||
QObject::connect(&this->pCanPlay, &AbstractDBusProperty::changed, this, &MprisPlayer::canPlayChanged);
|
||||
QObject::connect(&this->pCanPause, &AbstractDBusProperty::changed, this, &MprisPlayer::canPauseChanged);
|
||||
|
||||
QObject::connect(&this->pCanPlay, &AbstractDBusProperty::changed, this, &MprisPlayer::canTogglePlayingChanged);
|
||||
QObject::connect(&this->pCanPause, &AbstractDBusProperty::changed, this, &MprisPlayer::canTogglePlayingChanged);
|
||||
|
||||
QObject::connect(&this->pPosition, &AbstractDBusProperty::changed, this, &MprisPlayer::onPositionChanged);
|
||||
QObject::connect(this->player, &DBusMprisPlayer::Seeked, this, &MprisPlayer::onSeek);
|
||||
QObject::connect(&this->pVolume, &AbstractDBusProperty::changed, this, &MprisPlayer::volumeChanged);
|
||||
QObject::connect(&this->pMetadata, &AbstractDBusProperty::changed, this, &MprisPlayer::onMetadataChanged);
|
||||
QObject::connect(&this->pPlaybackStatus, &AbstractDBusProperty::changed, this, &MprisPlayer::onPlaybackStatusChanged);
|
||||
QObject::connect(&this->pLoopStatus, &AbstractDBusProperty::changed, this, &MprisPlayer::onLoopStatusChanged);
|
||||
QObject::connect(&this->pRate, &AbstractDBusProperty::changed, this, &MprisPlayer::rateChanged);
|
||||
QObject::connect(&this->pMinRate, &AbstractDBusProperty::changed, this, &MprisPlayer::minRateChanged);
|
||||
QObject::connect(&this->pMaxRate, &AbstractDBusProperty::changed, this, &MprisPlayer::maxRateChanged);
|
||||
QObject::connect(&this->pShuffle, &AbstractDBusProperty::changed, this, &MprisPlayer::shuffleChanged);
|
||||
|
||||
QObject::connect(&this->playerProperties, &DBusPropertyGroup::getAllFinished, this, &MprisPlayer::onGetAllFinished);
|
||||
|
||||
// Ensure user triggered position updates can update length.
|
||||
|
@ -151,41 +196,22 @@ void MprisPlayer::seek(qreal offset) {
|
|||
bool MprisPlayer::isValid() const { return this->player->isValid(); }
|
||||
QString MprisPlayer::address() const { return this->player->service(); }
|
||||
|
||||
bool MprisPlayer::canControl() const { return this->pCanControl.get(); }
|
||||
bool MprisPlayer::canPlay() const { return this->canControl() && this->pCanPlay.get(); }
|
||||
bool MprisPlayer::canPause() const { return this->canControl() && this->pCanPause.get(); }
|
||||
|
||||
bool MprisPlayer::canTogglePlaying() const {
|
||||
if (this->mPlaybackState == MprisPlaybackState::Playing) return this->canPlay();
|
||||
else return this->canPause();
|
||||
}
|
||||
|
||||
bool MprisPlayer::canSeek() const { return this->canControl() && this->pCanSeek.get(); }
|
||||
bool MprisPlayer::canGoNext() const { return this->canControl() && this->pCanGoNext.get(); }
|
||||
bool MprisPlayer::canGoPrevious() const { return this->canControl() && this->pCanGoPrevious.get(); }
|
||||
bool MprisPlayer::canQuit() const { return this->pCanQuit.get(); }
|
||||
bool MprisPlayer::canRaise() const { return this->pCanRaise.get(); }
|
||||
bool MprisPlayer::canSetFullscreen() const { return this->pCanSetFullscreen.get(); }
|
||||
|
||||
const QString& MprisPlayer::identity() const { return this->pIdentity.get(); }
|
||||
const QString& MprisPlayer::desktopEntry() const { return this->pDesktopEntry.get(); }
|
||||
|
||||
qlonglong MprisPlayer::positionMs() const {
|
||||
if (!this->positionSupported()) return 0; // unsupported
|
||||
if (this->mPlaybackState == MprisPlaybackState::Stopped) return 0;
|
||||
if (this->bPlaybackState == MprisPlaybackState::Stopped) return 0;
|
||||
|
||||
auto paused = this->mPlaybackState == MprisPlaybackState::Paused;
|
||||
auto paused = this->bPlaybackState == MprisPlaybackState::Paused;
|
||||
auto time = paused ? this->pausedTime : QDateTime::currentDateTime();
|
||||
auto offset = time - this->lastPositionTimestamp;
|
||||
auto rateMul = static_cast<qlonglong>(this->pRate.get() * 1000);
|
||||
auto rateMul = static_cast<qlonglong>(this->bRate.value() * 1000);
|
||||
offset = (offset * rateMul) / 1000;
|
||||
|
||||
return (this->pPosition.get() / 1000) + offset.count();
|
||||
return (this->bpPosition.value() / 1000) + offset.count();
|
||||
}
|
||||
|
||||
qreal MprisPlayer::position() const {
|
||||
if (!this->positionSupported()) return 0; // unsupported
|
||||
if (this->mPlaybackState == MprisPlaybackState::Stopped) return 0;
|
||||
if (this->bPlaybackState == MprisPlaybackState::Stopped) return 0;
|
||||
|
||||
return static_cast<qreal>(this->positionMs()) / 1000.0; // NOLINT
|
||||
}
|
||||
|
@ -193,7 +219,7 @@ qreal MprisPlayer::position() const {
|
|||
bool MprisPlayer::positionSupported() const { return this->pPosition.exists(); }
|
||||
|
||||
void MprisPlayer::setPosition(qreal position) {
|
||||
if (this->pPosition.get() == -1) {
|
||||
if (this->bpPosition.value() == -1) {
|
||||
qWarning() << "Cannot set position of" << this << "because position is not supported.";
|
||||
return;
|
||||
}
|
||||
|
@ -212,10 +238,10 @@ void MprisPlayer::setPosition(qreal position) {
|
|||
this->player->Seek(target - pos);
|
||||
}
|
||||
|
||||
this->pPosition.set(target);
|
||||
this->bpPosition = target;
|
||||
}
|
||||
|
||||
void MprisPlayer::onPositionChanged() {
|
||||
void MprisPlayer::onPositionUpdated() {
|
||||
const bool firstChange = !this->lastPositionTimestamp.isValid();
|
||||
this->lastPositionTimestamp = QDateTime::currentDateTimeUtc();
|
||||
this->pausedTime = this->lastPositionTimestamp;
|
||||
|
@ -227,19 +253,18 @@ void MprisPlayer::onExportedPositionChanged() {
|
|||
if (!this->lengthSupported()) emit this->lengthChanged();
|
||||
}
|
||||
|
||||
void MprisPlayer::onSeek(qlonglong time) { this->pPosition.set(time); }
|
||||
void MprisPlayer::onSeek(qlonglong time) { this->bpPosition = time; }
|
||||
|
||||
qreal MprisPlayer::length() const {
|
||||
if (this->mLength == -1) {
|
||||
if (this->bInternalLength == -1) {
|
||||
return this->position(); // unsupported
|
||||
} else {
|
||||
return static_cast<qreal>(this->mLength / 1000) / 1000; // NOLINT
|
||||
return static_cast<qreal>(this->bInternalLength / 1000) / 1000; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
bool MprisPlayer::lengthSupported() const { return this->mLength != -1; }
|
||||
bool MprisPlayer::lengthSupported() const { return this->bInternalLength != -1; }
|
||||
|
||||
qreal MprisPlayer::volume() const { return this->pVolume.get(); }
|
||||
bool MprisPlayer::volumeSupported() const { return this->pVolume.exists(); }
|
||||
|
||||
void MprisPlayer::setVolume(qreal volume) {
|
||||
|
@ -253,21 +278,15 @@ void MprisPlayer::setVolume(qreal volume) {
|
|||
return;
|
||||
}
|
||||
|
||||
this->pVolume.set(volume);
|
||||
this->bVolume = volume;
|
||||
this->pVolume.write();
|
||||
}
|
||||
|
||||
void MprisPlayer::onMetadataChanged() {
|
||||
auto lengthVariant = this->pMetadata.get().value("mpris:length");
|
||||
qlonglong length = -1;
|
||||
if (lengthVariant.isValid() && lengthVariant.canConvert<qlonglong>()) {
|
||||
length = lengthVariant.value<qlonglong>();
|
||||
}
|
||||
|
||||
auto trackChanged = false;
|
||||
|
||||
QString trackId;
|
||||
auto trackidVariant = this->pMetadata.get().value("mpris:trackid");
|
||||
auto trackidVariant = this->bpMetadata.value().value("mpris:trackid");
|
||||
if (trackidVariant.isValid()) {
|
||||
if (trackidVariant.canConvert<QString>()) {
|
||||
trackId = trackidVariant.toString();
|
||||
|
@ -282,7 +301,7 @@ void MprisPlayer::onMetadataChanged() {
|
|||
}
|
||||
|
||||
// Helps to catch players without trackid.
|
||||
auto urlVariant = this->pMetadata.get().value("xesam:url");
|
||||
auto urlVariant = this->bpMetadata.value().value("xesam:url");
|
||||
if (urlVariant.isValid() && urlVariant.canConvert<QString>()) {
|
||||
auto url = urlVariant.toString();
|
||||
|
||||
|
@ -292,47 +311,25 @@ void MprisPlayer::onMetadataChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
if (trackChanged) {
|
||||
emit this->trackChanged();
|
||||
}
|
||||
|
||||
Qt::beginPropertyUpdateGroup();
|
||||
|
||||
this->bMetadata = this->pMetadata.get();
|
||||
|
||||
auto trackTitle = this->pMetadata.get().value("xesam:title").toString();
|
||||
this->bTrackTitle = trackTitle.isNull() ? "Unknown Track" : trackTitle;
|
||||
|
||||
auto trackArtist = this->pMetadata.get().value("xesam:artist").value<QVector<QString>>();
|
||||
this->bTrackArtist = trackArtist.join(", ");
|
||||
|
||||
auto trackAlbum = this->pMetadata.get().value("xesam:album").toString();
|
||||
this->bTrackAlbum = trackAlbum.isNull() ? "Unknown Album" : trackAlbum;
|
||||
|
||||
this->bTrackAlbumArtist = this->pMetadata.get().value("xesam:albumArtist").toString();
|
||||
this->bTrackArtUrl = this->pMetadata.get().value("mpris:artUrl").toString();
|
||||
|
||||
if (trackChanged) {
|
||||
emit this->trackChanged();
|
||||
this->bUniqueId = this->bUniqueId + 1;
|
||||
|
||||
// Some players don't seem to send position updates or seeks on track change.
|
||||
this->pPosition.update();
|
||||
this->pPosition.requestUpdate();
|
||||
}
|
||||
|
||||
Qt::endPropertyUpdateGroup();
|
||||
this->bMetadata = this->bpMetadata.value();
|
||||
|
||||
this->setLength(length);
|
||||
Qt::endPropertyUpdateGroup();
|
||||
|
||||
emit this->postTrackChanged();
|
||||
}
|
||||
|
||||
DEFINE_MEMBER_SET(MprisPlayer, length, setLength);
|
||||
|
||||
MprisPlaybackState::Enum MprisPlayer::playbackState() const { return this->mPlaybackState; }
|
||||
|
||||
void MprisPlayer::setPlaybackState(MprisPlaybackState::Enum playbackState) {
|
||||
if (playbackState == this->mPlaybackState) return;
|
||||
if (playbackState == this->bPlaybackState) return;
|
||||
|
||||
switch (playbackState) {
|
||||
case MprisPlaybackState::Stopped:
|
||||
|
@ -371,38 +368,13 @@ void MprisPlayer::pause() { this->setPlaybackState(MprisPlaybackState::Paused);
|
|||
void MprisPlayer::stop() { this->setPlaybackState(MprisPlaybackState::Stopped); }
|
||||
|
||||
void MprisPlayer::togglePlaying() {
|
||||
if (this->mPlaybackState == MprisPlaybackState::Playing) {
|
||||
if (this->bPlaybackState == MprisPlaybackState::Playing) {
|
||||
this->pause();
|
||||
} else {
|
||||
this->play();
|
||||
}
|
||||
}
|
||||
|
||||
void MprisPlayer::onPlaybackStatusChanged() {
|
||||
const auto& status = this->pPlaybackStatus.get();
|
||||
|
||||
auto state = MprisPlaybackState::Stopped;
|
||||
if (status == "Playing") {
|
||||
state = MprisPlaybackState::Playing;
|
||||
} else if (status == "Paused") {
|
||||
this->pausedTime = QDateTime::currentDateTimeUtc();
|
||||
state = MprisPlaybackState::Paused;
|
||||
} else if (status == "Stopped") {
|
||||
state = MprisPlaybackState::Stopped;
|
||||
} else {
|
||||
state = MprisPlaybackState::Stopped;
|
||||
qWarning() << "Received unexpected PlaybackStatus for" << this << status;
|
||||
}
|
||||
|
||||
if (state != this->mPlaybackState) {
|
||||
// make sure we're in sync at least on play/pause. Some players don't automatically send this.
|
||||
this->pPosition.update();
|
||||
this->mPlaybackState = state;
|
||||
emit this->playbackStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
MprisLoopState::Enum MprisPlayer::loopState() const { return this->mLoopState; }
|
||||
bool MprisPlayer::loopSupported() const { return this->pLoopStatus.exists(); }
|
||||
|
||||
void MprisPlayer::setLoopState(MprisLoopState::Enum loopState) {
|
||||
|
@ -416,7 +388,7 @@ void MprisPlayer::setLoopState(MprisLoopState::Enum loopState) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (loopState == this->mLoopState) return;
|
||||
if (loopState == this->bLoopState) return;
|
||||
|
||||
QString loopStatusStr;
|
||||
switch (loopState) {
|
||||
|
@ -428,46 +400,24 @@ void MprisPlayer::setLoopState(MprisLoopState::Enum loopState) {
|
|||
return;
|
||||
}
|
||||
|
||||
this->pLoopStatus.set(loopStatusStr);
|
||||
this->bpLoopStatus = loopStatusStr;
|
||||
this->pLoopStatus.write();
|
||||
}
|
||||
|
||||
void MprisPlayer::onLoopStatusChanged() {
|
||||
const auto& status = this->pLoopStatus.get();
|
||||
|
||||
if (status == "None") {
|
||||
this->mLoopState = MprisLoopState::None;
|
||||
} else if (status == "Track") {
|
||||
this->mLoopState = MprisLoopState::Track;
|
||||
} else if (status == "Playlist") {
|
||||
this->mLoopState = MprisLoopState::Playlist;
|
||||
} else {
|
||||
this->mLoopState = MprisLoopState::None;
|
||||
qWarning() << "Received unexpected LoopStatus for" << this << status;
|
||||
}
|
||||
|
||||
emit this->loopStateChanged();
|
||||
}
|
||||
|
||||
qreal MprisPlayer::rate() const { return this->pRate.get(); }
|
||||
qreal MprisPlayer::minRate() const { return this->pMinRate.get(); }
|
||||
qreal MprisPlayer::maxRate() const { return this->pMaxRate.get(); }
|
||||
|
||||
void MprisPlayer::setRate(qreal rate) {
|
||||
if (rate == this->pRate.get()) return;
|
||||
if (rate == this->bRate.value()) return;
|
||||
|
||||
if (rate < this->pMinRate.get() || rate > this->pMaxRate.get()) {
|
||||
if (rate < this->bMinRate.value() || rate > this->bMaxRate.value()) {
|
||||
qWarning() << "Cannot set rate for" << this << "to" << rate
|
||||
<< "which is outside of minRate and maxRate" << this->pMinRate.get()
|
||||
<< this->pMaxRate.get();
|
||||
<< "which is outside of minRate and maxRate" << this->bMinRate.value()
|
||||
<< this->bMaxRate.value();
|
||||
return;
|
||||
}
|
||||
|
||||
this->pRate.set(rate);
|
||||
this->bRate = rate;
|
||||
this->pRate.write();
|
||||
}
|
||||
|
||||
bool MprisPlayer::shuffle() const { return this->pShuffle.get(); }
|
||||
bool MprisPlayer::shuffleSupported() const { return this->pShuffle.exists(); }
|
||||
|
||||
void MprisPlayer::setShuffle(bool shuffle) {
|
||||
|
@ -481,25 +431,20 @@ void MprisPlayer::setShuffle(bool shuffle) {
|
|||
return;
|
||||
}
|
||||
|
||||
this->pShuffle.set(shuffle);
|
||||
this->bShuffle = shuffle;
|
||||
this->pShuffle.write();
|
||||
}
|
||||
|
||||
bool MprisPlayer::fullscreen() const { return this->pFullscreen.get(); }
|
||||
|
||||
void MprisPlayer::setFullscreen(bool fullscreen) {
|
||||
if (!this->canSetFullscreen()) {
|
||||
qWarning() << "Cannot set fullscreen for" << this << "because canSetFullscreen is false.";
|
||||
return;
|
||||
}
|
||||
|
||||
this->pFullscreen.set(fullscreen);
|
||||
this->bFullscreen = fullscreen;
|
||||
this->pFullscreen.write();
|
||||
}
|
||||
|
||||
QList<QString> MprisPlayer::supportedUriSchemes() const { return this->pSupportedUriSchemes.get(); }
|
||||
QList<QString> MprisPlayer::supportedMimeTypes() const { return this->pSupportedMimeTypes.get(); }
|
||||
|
||||
void MprisPlayer::onGetAllFinished() {
|
||||
if (this->volumeSupported()) emit this->volumeSupportedChanged();
|
||||
if (this->loopSupported()) emit this->loopSupportedChanged();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue