From db9e6331973f32d70839ab48aef52be092a7d97f Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 20 Nov 2024 19:31:40 -0800 Subject: [PATCH] service/mpris: adopt bindable properties --- src/core/util.hpp | 21 +++ src/services/mpris/player.cpp | 263 ++++++++++++++-------------------- src/services/mpris/player.hpp | 229 +++++++++++++++++------------ 3 files changed, 263 insertions(+), 250 deletions(-) diff --git a/src/core/util.hpp b/src/core/util.hpp index 62264b93..84da1ac7 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -290,3 +290,24 @@ bool setSimpleObjectHandle(auto* parent, auto* value) { [[nodiscard]] Type getter() { return this->member.value(); } \ [[nodiscard]] QBindable bindable() { return &this->member; } // NOLINTEND + +template +class MethodFunctor { + using PtrMeta = MemberPointerTraits; + using Class = PtrMeta::Class; + +public: + MethodFunctor(Class* obj): obj(obj) {} + + void operator()() { (this->obj->*methodPtr)(); } + +private: + Class* obj; +}; + +// NOLINTBEGIN +#define QS_BINDING_SUBSCRIBE_METHOD(Class, bindable, method, strategy) \ + QPropertyChangeHandler> \ + _qs_method_subscribe_##bindable##_##method = \ + (bindable).strategy(MethodFunctor<&Class::method>(this)); +// NOLINTEND diff --git a/src/services/mpris/player.cpp b/src/services/mpris/player.cpp index a97020b0..b4caa18c 100644 --- a/src/services/mpris/player.cpp +++ b/src/services/mpris/player.cpp @@ -4,15 +4,16 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include -#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>(); + 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()) { + return variant.value(); + } else return static_cast(-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(this->pRate.get() * 1000); + auto rateMul = static_cast(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(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(this->mLength / 1000) / 1000; // NOLINT + return static_cast(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()) { - length = lengthVariant.value(); - } - 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()) { 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()) { 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>(); - 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 MprisPlayer::supportedUriSchemes() const { return this->pSupportedUriSchemes.get(); } -QList MprisPlayer::supportedMimeTypes() const { return this->pSupportedMimeTypes.get(); } - void MprisPlayer::onGetAllFinished() { if (this->volumeSupported()) emit this->volumeSupportedChanged(); if (this->loopSupported()) emit this->loopSupportedChanged(); diff --git a/src/services/mpris/player.hpp b/src/services/mpris/player.hpp index b509980b..43545f8e 100644 --- a/src/services/mpris/player.hpp +++ b/src/services/mpris/player.hpp @@ -63,20 +63,20 @@ public: class MprisPlayer: public QObject { Q_OBJECT; // clang-format off - Q_PROPERTY(bool canControl READ canControl NOTIFY canControlChanged); - Q_PROPERTY(bool canPlay READ canPlay NOTIFY canPlayChanged); - Q_PROPERTY(bool canPause READ canPause NOTIFY canPauseChanged); - Q_PROPERTY(bool canTogglePlaying READ canTogglePlaying NOTIFY canTogglePlayingChanged); - Q_PROPERTY(bool canSeek READ canSeek NOTIFY canSeekChanged); - Q_PROPERTY(bool canGoNext READ canGoNext NOTIFY canGoNextChanged); - Q_PROPERTY(bool canGoPrevious READ canGoPrevious NOTIFY canGoPreviousChanged); - Q_PROPERTY(bool canQuit READ canQuit NOTIFY canQuitChanged); - Q_PROPERTY(bool canRaise READ canRaise NOTIFY canRaiseChanged); - Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged); + Q_PROPERTY(bool canControl READ canControl NOTIFY canControlChanged BINDABLE bindableCanControl); + Q_PROPERTY(bool canPlay READ canPlay NOTIFY canPlayChanged BINDABLE bindableCanPlay); + Q_PROPERTY(bool canPause READ canPause NOTIFY canPauseChanged BINDABLE bindableCanPause); + Q_PROPERTY(bool canTogglePlaying READ canTogglePlaying NOTIFY canTogglePlayingChanged BINDABLE bindableCanTogglePlaying); + Q_PROPERTY(bool canSeek READ canSeek NOTIFY canSeekChanged BINDABLE bindableCanSeek); + Q_PROPERTY(bool canGoNext READ canGoNext NOTIFY canGoNextChanged BINDABLE bindableCanGoNext); + Q_PROPERTY(bool canGoPrevious READ canGoPrevious NOTIFY canGoPreviousChanged BINDABLE bindableCanGoPrevious); + Q_PROPERTY(bool canQuit READ canQuit NOTIFY canQuitChanged BINDABLE bindableCanQuit BINDABLE bindableCanQuit); + Q_PROPERTY(bool canRaise READ canRaise NOTIFY canRaiseChanged BINDABLE bindableCanRaise BINDABLE bindableCanRaise); + Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged BINDABLE bindableCanSetFullscreen); /// The human readable name of the media player. - Q_PROPERTY(QString identity READ identity NOTIFY identityChanged); + Q_PROPERTY(QString identity READ identity NOTIFY identityChanged BINDABLE bindableIdentity); /// The name of the desktop entry for the media player, or an empty string if not provided. - Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged); + Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged BINDABLE bindableDesktopEntry); /// The current position in the playing track, as seconds, with millisecond precision, /// or `0` if @@positionSupported is false. /// @@ -119,7 +119,7 @@ class MprisPlayer: public QObject { /// The volume of the playing track from 0.0 to 1.0, or 1.0 if @@volumeSupported is false. /// /// May only be written to if @@canControl and @@volumeSupported are true. - Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged); + Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged BINDABLE bindableVolume); Q_PROPERTY(bool volumeSupported READ volumeSupported NOTIFY volumeSupportedChanged); /// Metadata of the current track. /// @@ -134,7 +134,7 @@ class MprisPlayer: public QObject { /// /// > [!WARNING] This is NOT `mpris:trackid` as that is sometimes missing or nonunique /// > in some players. - Q_PROPERTY(quint32 uniqueId READ uniqueId NOTIFY trackChanged BINDABLE bindableUniqueId); + Q_PROPERTY(quint32 uniqueId READ uniqueId NOTIFY uniqueIdChanged BINDABLE bindableUniqueId); /// The title of the current track, or "Unknown Track" if none was provided. Q_PROPERTY(QString trackTitle READ trackTitle NOTIFY trackTitleChanged BINDABLE bindableTrackTitle); /// The current track's artist, or an empty string if none was provided. @@ -153,11 +153,11 @@ class MprisPlayer: public QObject { /// - If @@canPause is false, you cannot assign the `Paused` state. /// - If @@canControl is false, you cannot assign the `Stopped` state. /// (or any of the others, though their repsective properties will also be false) - Q_PROPERTY(qs::service::mpris::MprisPlaybackState::Enum playbackState READ playbackState WRITE setPlaybackState NOTIFY playbackStateChanged); + Q_PROPERTY(qs::service::mpris::MprisPlaybackState::Enum playbackState READ playbackState WRITE setPlaybackState NOTIFY playbackStateChanged BINDABLE bindablePlaybackState); /// The loop state of the media player, or `None` if @@loopSupported is false. /// /// May only be written to if @@canControl and @@loopSupported are true. - Q_PROPERTY(qs::service::mpris::MprisLoopState::Enum loopState READ loopState WRITE setLoopState NOTIFY loopStateChanged); + Q_PROPERTY(qs::service::mpris::MprisLoopState::Enum loopState READ loopState WRITE setLoopState NOTIFY loopStateChanged BINDABLE bindableLoopState); Q_PROPERTY(bool loopSupported READ loopSupported NOTIFY loopSupportedChanged); /// The speed the song is playing at, as a multiplier. /// @@ -165,22 +165,22 @@ class MprisPlayer: public QObject { /// Additionally, It is recommended that you only write common values such as `0.25`, `0.5`, `1.0`, `2.0` /// to the property, as media players are free to ignore the value, and are more likely to /// accept common ones. - Q_PROPERTY(qreal rate READ rate WRITE setRate NOTIFY rateChanged); - Q_PROPERTY(qreal minRate READ minRate NOTIFY minRateChanged); - Q_PROPERTY(qreal maxRate READ maxRate NOTIFY maxRateChanged); + Q_PROPERTY(qreal rate READ rate WRITE setRate NOTIFY rateChanged BINDABLE bindableRate); + Q_PROPERTY(qreal minRate READ minRate NOTIFY minRateChanged BINDABLE bindableMinRate); + Q_PROPERTY(qreal maxRate READ maxRate NOTIFY maxRateChanged BINDABLE bindableMaxRate); /// If the play queue is currently being shuffled, or false if @@shuffleSupported is false. /// /// May only be written if @@canControl and @@shuffleSupported are true. - Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged); + Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged BINDABLE bindableShuffle); Q_PROPERTY(bool shuffleSupported READ shuffleSupported NOTIFY shuffleSupportedChanged); /// If the player is currently shown in fullscreen. /// /// May only be written to if @@canSetFullscreen is true. - Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged); + Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged BINDABLE bindableFullscreen); /// Uri schemes supported by @@openUri(). - Q_PROPERTY(QList supportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemesChanged); + Q_PROPERTY(QList supportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemesChanged BINDABLE bindableSupportedUriSchemes); /// Mime types supported by @@openUri(). - Q_PROPERTY(QList supportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged); + Q_PROPERTY(QList supportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged BINDABLE bindableSupportedMimeTypes); // clang-format on QML_ELEMENT; QML_UNCREATABLE("MprisPlayers can only be acquired from Mpris"); @@ -231,19 +231,19 @@ public: [[nodiscard]] bool isValid() const; [[nodiscard]] QString address() const; - [[nodiscard]] bool canControl() const; - [[nodiscard]] bool canSeek() const; - [[nodiscard]] bool canGoNext() const; - [[nodiscard]] bool canGoPrevious() const; - [[nodiscard]] bool canPlay() const; - [[nodiscard]] bool canPause() const; - [[nodiscard]] bool canTogglePlaying() const; - [[nodiscard]] bool canQuit() const; - [[nodiscard]] bool canRaise() const; - [[nodiscard]] bool canSetFullscreen() const; + QS_BINDABLE_GETTER(bool, bCanControl, canControl, bindableCanControl); + QS_BINDABLE_GETTER(bool, bCanSeek, canSeek, bindableCanSeek); + QS_BINDABLE_GETTER(bool, bCanGoNext, canGoNext, bindableCanGoNext); + QS_BINDABLE_GETTER(bool, bCanGoPrevious, canGoPrevious, bindableCanGoPrevious); + QS_BINDABLE_GETTER(bool, bCanPlay, canPlay, bindableCanPlay); + QS_BINDABLE_GETTER(bool, bCanPause, canPause, bindableCanPause); + QS_BINDABLE_GETTER(bool, bCanTogglePlaying, canTogglePlaying, bindableCanTogglePlaying); + QS_BINDABLE_GETTER(bool, bCanQuit, canQuit, bindableCanQuit); + QS_BINDABLE_GETTER(bool, bCanRaise, canRaise, bindableCanRaise); + QS_BINDABLE_GETTER(bool, bCanSetFullscreen, canSetFullscreen, bindableCanSetFullscreen); - [[nodiscard]] const QString& identity() const; - [[nodiscard]] const QString& desktopEntry() const; + QS_BINDABLE_GETTER(QString, bIdentity, identity, bindableIdentity); + QS_BINDABLE_GETTER(QString, bDesktopEntry, desktopEntry, bindableDesktopEntry); [[nodiscard]] qlonglong positionMs() const; [[nodiscard]] qreal position() const; @@ -253,7 +253,7 @@ public: [[nodiscard]] qreal length() const; [[nodiscard]] bool lengthSupported() const; - [[nodiscard]] qreal volume() const; + QS_BINDABLE_GETTER(qreal, bVolume, volume, bindableVolume); [[nodiscard]] bool volumeSupported() const; void setVolume(qreal volume); @@ -265,27 +265,44 @@ public: QS_BINDABLE_GETTER(QString, bTrackArtist, trackArtist, bindableTrackArtist); QS_BINDABLE_GETTER(QString, bTrackArtUrl, trackArtUrl, bindableTrackArtUrl); - [[nodiscard]] MprisPlaybackState::Enum playbackState() const; + QS_BINDABLE_GETTER( + MprisPlaybackState::Enum, + bPlaybackState, + playbackState, + bindablePlaybackState + ); + void setPlaybackState(MprisPlaybackState::Enum playbackState); - [[nodiscard]] MprisLoopState::Enum loopState() const; + QS_BINDABLE_GETTER(MprisLoopState::Enum, bLoopState, loopState, bindableLoopState); [[nodiscard]] bool loopSupported() const; void setLoopState(MprisLoopState::Enum loopState); - [[nodiscard]] qreal rate() const; - [[nodiscard]] qreal minRate() const; - [[nodiscard]] qreal maxRate() const; + QS_BINDABLE_GETTER(qreal, bRate, rate, bindableRate); + QS_BINDABLE_GETTER(qreal, bRate, minRate, bindableMinRate); + QS_BINDABLE_GETTER(qreal, bRate, maxRate, bindableMaxRate); void setRate(qreal rate); - [[nodiscard]] bool shuffle() const; + QS_BINDABLE_GETTER(bool, bShuffle, shuffle, bindableShuffle); [[nodiscard]] bool shuffleSupported() const; void setShuffle(bool shuffle); - [[nodiscard]] bool fullscreen() const; + QS_BINDABLE_GETTER(bool, bFullscreen, fullscreen, bindableFullscreen); void setFullscreen(bool fullscreen); - [[nodiscard]] QList supportedUriSchemes() const; - [[nodiscard]] QList supportedMimeTypes() const; + QS_BINDABLE_GETTER( + QList, + bSupportedUriSchemes, + supportedUriSchemes, + bindableSupportedUriSchemes + ); + + QS_BINDABLE_GETTER( + QList, + bSupportedMimeTypes, + supportedMimeTypes, + bindableSupportedMimeTypes + ); signals: /// The track has changed. @@ -327,6 +344,7 @@ signals: void volumeChanged(); void volumeSupportedChanged(); void metadataChanged(); + void uniqueIdChanged(); void trackTitleChanged(); void trackArtistChanged(); void trackAlbumChanged(); @@ -346,66 +364,95 @@ signals: private slots: void onGetAllFinished(); - void onPositionChanged(); void onExportedPositionChanged(); void onSeek(qlonglong time); - void onMetadataChanged(); - void onPlaybackStatusChanged(); - void onLoopStatusChanged(); private: - // clang-format off - dbus::DBusPropertyGroup appProperties; - dbus::DBusProperty pIdentity {this->appProperties, "Identity"}; - dbus::DBusProperty pDesktopEntry {this->appProperties, "DesktopEntry", "", false}; - dbus::DBusProperty pCanQuit {this->appProperties, "CanQuit"}; - dbus::DBusProperty pCanRaise {this->appProperties, "CanRaise"}; - dbus::DBusProperty pFullscreen {this->appProperties, "Fullscreen", false, false}; - dbus::DBusProperty pCanSetFullscreen {this->appProperties, "CanSetFullscreen", false, false}; - dbus::DBusProperty> pSupportedUriSchemes {this->appProperties, "SupportedUriSchemes"}; - dbus::DBusProperty> pSupportedMimeTypes {this->appProperties, "SupportedMimeTypes"}; + void onMetadataChanged(); + void onPositionUpdated(); + void requestPositionUpdate() { this->pPosition.requestUpdate(); }; - dbus::DBusPropertyGroup playerProperties; - dbus::DBusProperty pCanControl {this->playerProperties, "CanControl"}; - dbus::DBusProperty pCanPlay {this->playerProperties, "CanPlay"}; - dbus::DBusProperty pCanPause {this->playerProperties, "CanPause"}; - dbus::DBusProperty pCanSeek {this->playerProperties, "CanSeek"}; - dbus::DBusProperty pCanGoNext {this->playerProperties, "CanGoNext"}; - dbus::DBusProperty pCanGoPrevious {this->playerProperties, "CanGoPrevious"}; - dbus::DBusProperty pPosition {this->playerProperties, "Position", 0, false}; // "required" - dbus::DBusProperty pVolume {this->playerProperties, "Volume", 1, false}; // "required" - dbus::DBusProperty pMetadata {this->playerProperties, "Metadata"}; - dbus::DBusProperty pPlaybackStatus {this->playerProperties, "PlaybackStatus"}; - dbus::DBusProperty pLoopStatus {this->playerProperties, "LoopStatus", "", false}; - dbus::DBusProperty pRate {this->playerProperties, "Rate", 1, false}; // "required" - dbus::DBusProperty pMinRate {this->playerProperties, "MinimumRate", 1, false}; // "required" - dbus::DBusProperty pMaxRate {this->playerProperties, "MaximumRate", 1, false}; // "required" - dbus::DBusProperty pShuffle {this->playerProperties, "Shuffle", false, false}; + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bIdentity, &MprisPlayer::identityChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bDesktopEntry, &MprisPlayer::desktopEntryChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanQuit, &MprisPlayer::canQuitChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanRaise, &MprisPlayer::canRaiseChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bFullscreen, &MprisPlayer::fullscreenChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanSetFullscreen, &MprisPlayer::canSetFullscreenChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QList, bSupportedUriSchemes, &MprisPlayer::supportedUriSchemesChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QList, bSupportedMimeTypes, &MprisPlayer::supportedMimeTypesChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(MprisPlayer, appProperties); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pIdentity, bIdentity, appProperties, "Identity"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pDesktopEntry, bDesktopEntry, appProperties, "DesktopEntry"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanQuit, bCanQuit, appProperties, "CanQuit"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanRaise, bCanRaise, appProperties, "CanRaise"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pFullscreen, bFullscreen, appProperties, "Fullscreen"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanSetFullscreen, bCanSetFullscreen, appProperties, "CanSetFullscreen"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pSupportedUriSchemes, bSupportedUriSchemes, appProperties, "SupportedUriSchemes"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pSupportedMimeTypes, bSupportedMimeTypes, appProperties, "SupportedMimeTypes"); + + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanPlay); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanPause); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanSeek); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanGoNext); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanGoPrevious); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QVariantMap, bpMetadata); + QS_BINDING_SUBSCRIBE_METHOD(MprisPlayer, bpMetadata, onMetadataChanged, onValueChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MprisPlayer, qlonglong, bpPosition, -1, &MprisPlayer::positionChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bpPlaybackStatus); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bpLoopStatus); + + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanControl, &MprisPlayer::canControlChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanPlay, &MprisPlayer::canPlayChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanPause, &MprisPlayer::canPauseChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanTogglePlaying, &MprisPlayer::canTogglePlayingChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanSeek, &MprisPlayer::canSeekChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanGoNext, &MprisPlayer::canGoNextChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanGoPrevious, &MprisPlayer::canGoPreviousChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MprisPlayer, qreal, bVolume, 1, &MprisPlayer::volumeChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, MprisPlaybackState::Enum, bPlaybackState, &MprisPlayer::playbackStateChanged); + QS_BINDING_SUBSCRIBE_METHOD(MprisPlayer, bPlaybackState, requestPositionUpdate, onValueChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, MprisLoopState::Enum, bLoopState, &MprisPlayer::loopStateChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MprisPlayer, qreal, bRate, 1, &MprisPlayer::rateChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MprisPlayer, qreal, bMinRate, 1, &MprisPlayer::minRateChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MprisPlayer, qreal, bMaxRate, 1, &MprisPlayer::maxRateChanged); + + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QVariantMap, bMetadata, &MprisPlayer::metadataChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, quint32, bUniqueId, &MprisPlayer::uniqueIdChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackTitle, &MprisPlayer::trackTitleChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackArtist, &MprisPlayer::trackArtistChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackAlbum, &MprisPlayer::trackAlbumChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackAlbumArtist, &MprisPlayer::trackAlbumArtistChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackArtUrl, &MprisPlayer::trackArtUrlChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, qlonglong, bInternalLength, &MprisPlayer::lengthChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bShuffle, &MprisPlayer::shuffleChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(MprisPlayer, playerProperties); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanControl, bCanControl, playerProperties, "CanControl"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanPlay, bpCanPlay, playerProperties, "CanPlay"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanPause, bpCanPause, playerProperties, "CanPause"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanSeek, bpCanSeek, playerProperties, "CanSeek"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanGoNext, bpCanGoNext, playerProperties, "CanGoNext"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanGoPrevious, bpCanGoPrevious, playerProperties, "CanGoPrevious"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pPosition, bpPosition, onPositionUpdated, playerProperties, "Position", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pVolume, bVolume, playerProperties, "Volume", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMetadata, bpMetadata, playerProperties, "Metadata"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pPlaybackStatus, bpPlaybackStatus, playerProperties, "PlaybackStatus"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pLoopStatus, bpLoopStatus, playerProperties, "LoopStatus", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pRate, bRate, playerProperties, "Rate", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMinRate, bMinRate, playerProperties, "MinimumRate", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMaxRate, bMaxRate, playerProperties, "MaximumRate", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pShuffle, bShuffle, playerProperties, "Shuffle", false); // clang-format on - MprisPlaybackState::Enum mPlaybackState = MprisPlaybackState::Stopped; - MprisLoopState::Enum mLoopState = MprisLoopState::None; QDateTime lastPositionTimestamp; QDateTime pausedTime; - qlonglong mLength = -1; DBusMprisPlayerApp* app = nullptr; DBusMprisPlayer* player = nullptr; QString mTrackId; QString mTrackUrl; - - DECLARE_MEMBER(MprisPlayer, length, mLength, lengthChanged); - DECLARE_MEMBER_SET(length, setLength); - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, quint32, bUniqueId); - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QVariantMap, bMetadata, &MprisPlayer::metadataChanged); - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackArtist, &MprisPlayer::trackArtistChanged); - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackTitle, &MprisPlayer::trackTitleChanged); - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackAlbum, &MprisPlayer::trackAlbumChanged); - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackAlbumArtist, &MprisPlayer::trackAlbumArtistChanged); - Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bTrackArtUrl, &MprisPlayer::trackArtUrlChanged); - // clang-format on }; } // namespace qs::service::mpris