forked from quickshell/quickshell
		
	feat: mpris
This commit is contained in:
		
							parent
							
								
									73cfeba61b
								
							
						
					
					
						commit
						3b6d1c3bd8
					
				
					 11 changed files with 749 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -5,3 +5,7 @@ endif()
 | 
			
		|||
if (SERVICE_PIPEWIRE)
 | 
			
		||||
	add_subdirectory(pipewire)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (SERVICE_MPRIS)
 | 
			
		||||
	add_subdirectory(mpris)
 | 
			
		||||
endif()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										47
									
								
								src/services/mpris/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/services/mpris/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
qt_add_dbus_adaptor(DBUS_INTERFACES
 | 
			
		||||
	org.mpris.MprisWatcher.xml
 | 
			
		||||
	watcher.hpp
 | 
			
		||||
	qs::service::mp::MprisWatcher
 | 
			
		||||
	dbus_watcher
 | 
			
		||||
	MprisWatcherAdaptor
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(org.mpris.MediaPlayer2.Player.xml PROPERTIES
 | 
			
		||||
	CLASSNAME DBusMprisPlayer	
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_dbus_interface(DBUS_INTERFACES
 | 
			
		||||
	org.mpris.MediaPlayer2.Player.xml
 | 
			
		||||
	dbus_player
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(org.mpris.MprisWatcher.xml PROPERTIES
 | 
			
		||||
	CLASSNAME DBusMprisWatcher
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_dbus_interface(DBUS_INTERFACES
 | 
			
		||||
	org.mpris.MprisWatcher.xml
 | 
			
		||||
	dbus_watcher_interface
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_library(quickshell-service-mpris STATIC
 | 
			
		||||
	qml.cpp
 | 
			
		||||
 | 
			
		||||
	watcher.cpp
 | 
			
		||||
	player.cpp
 | 
			
		||||
	${DBUS_INTERFACES}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# dbus headers
 | 
			
		||||
target_include_directories(quickshell-service-mpris PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
 | 
			
		||||
 | 
			
		||||
qt_add_qml_module(quickshell-service-mpris
 | 
			
		||||
	URI Quickshell.Services.Mpris
 | 
			
		||||
  VERSION 0.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(quickshell-service-mpris PRIVATE ${QT_DEPS} quickshell-dbus)
 | 
			
		||||
target_link_libraries(quickshell PRIVATE quickshell-service-mprisplugin)
 | 
			
		||||
 | 
			
		||||
qs_pch(quickshell-service-mpris)
 | 
			
		||||
qs_pch(quickshell-service-mprisplugin)
 | 
			
		||||
							
								
								
									
										33
									
								
								src/services/mpris/org.mpris.MediaPlayer2.Player.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/services/mpris/org.mpris.MediaPlayer2.Player.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
<node>
 | 
			
		||||
  <interface name='org.mpris.MediaPlayer2.Player'>
 | 
			
		||||
    <property name='CanControl' type='b' 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='Metadata' type='a{sv}' access='read'>
 | 
			
		||||
      <annotation name='org.qtproject.QtDBus.QtTypeName' value='QVariantMap'/>
 | 
			
		||||
    </property>
 | 
			
		||||
		<property name='PlaybackStatus' type='s' access='read'/>
 | 
			
		||||
		<property name='Shuffle' type='b' access='readwrite' />
 | 
			
		||||
		<property name='LoopStatus' type='s' access='readwrite' />
 | 
			
		||||
		<property name='Rate' 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' />
 | 
			
		||||
    <method name='SetPosition'>
 | 
			
		||||
        <arg direction='in' type='o' name='TrackId'/>
 | 
			
		||||
        <arg direction='in' type='x' name='Position'/>
 | 
			
		||||
		</method>
 | 
			
		||||
		<method name='Seek'>
 | 
			
		||||
			<arg direction='in' type='x' name='Offset' /> 
 | 
			
		||||
		</method>
 | 
			
		||||
		<method name='PlayPause' />
 | 
			
		||||
    <method name='Next' />
 | 
			
		||||
    <method name='Previous' />
 | 
			
		||||
    <method name='Stop' />
 | 
			
		||||
		<method name='Play' />
 | 
			
		||||
		<method name='Pause' />
 | 
			
		||||
  </interface>
 | 
			
		||||
</node>
 | 
			
		||||
							
								
								
									
										16
									
								
								src/services/mpris/org.mpris.MprisWatcher.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/services/mpris/org.mpris.MprisWatcher.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
<node>
 | 
			
		||||
  <interface name='org.mpris.MprisWatcher'>
 | 
			
		||||
    <property name='ProtocolVersion' type='i' access='read'/>
 | 
			
		||||
    <property name='RegisteredMprisPlayers' type='as' access='read'/>	
 | 
			
		||||
    <method name='RegisterMprisPlayer'>
 | 
			
		||||
      <arg name='service' type='s' direction='in'/>
 | 
			
		||||
    </method> 
 | 
			
		||||
    <signal name='MprisPlayerRegistered'>
 | 
			
		||||
      <arg type='s' direction='out' name='service'/>
 | 
			
		||||
    </signal>
 | 
			
		||||
    <signal name='MprisPlayerUnregistered'>
 | 
			
		||||
      <arg type='s' direction='out' name='service'/>
 | 
			
		||||
    </signal> 
 | 
			
		||||
  </interface>
 | 
			
		||||
</node>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										87
									
								
								src/services/mpris/player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/services/mpris/player.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
#include "player.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qdbusextratypes.h>
 | 
			
		||||
#include <qdbusmetatype.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qstring.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/properties.hpp"
 | 
			
		||||
#include "dbus_player.h"
 | 
			
		||||
 | 
			
		||||
using namespace qs::dbus;
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logMprisPlayer, "quickshell.service.mp.player", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::mp {
 | 
			
		||||
 | 
			
		||||
MprisPlayer::MprisPlayer(const QString& address, QObject* parent)
 | 
			
		||||
    : QObject(parent)
 | 
			
		||||
    , watcherId(address) {
 | 
			
		||||
	// qDBusRegisterMetaType<DBusSniIconPixmap>();
 | 
			
		||||
	// qDBusRegisterMetaType<DBusSniIconPixmapList>();
 | 
			
		||||
	// qDBusRegisterMetaType<DBusSniTooltip>();
 | 
			
		||||
	// spec is unclear about what exactly an item address is, so account for both
 | 
			
		||||
	auto splitIdx = address.indexOf('/');
 | 
			
		||||
	auto conn = splitIdx == -1 ? address : address.sliced(0, splitIdx);
 | 
			
		||||
	auto path = splitIdx == -1 ? "/org/mpris/MediaPlayer2" : address.sliced(splitIdx);
 | 
			
		||||
 | 
			
		||||
	this->player = new DBusMprisPlayer(conn, path, QDBusConnection::sessionBus(), this);
 | 
			
		||||
 | 
			
		||||
	if (!this->player->isValid()) {
 | 
			
		||||
		qCWarning(logMprisPlayer).noquote() << "Cannot create MprisPlayer for" << conn;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(&this->canControl, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->canGoNext, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->canGoPrevious, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->canPlay, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->canPause, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->metadata, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer); 	
 | 
			
		||||
	QObject::connect(&this->playbackStatus, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);	
 | 
			
		||||
	QObject::connect(&this->position, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->minimumRate, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->maximumRate, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer); 
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->loopStatus, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->rate, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->shuffle, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
	QObject::connect(&this->volume, &AbstractDBusProperty::changed, this, &MprisPlayer::updatePlayer);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->properties, &DBusPropertyGroup::getAllFinished, this, &MprisPlayer::onGetAllFinished);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	this->properties.setInterface(this->player);
 | 
			
		||||
	this->properties.updateAllViaGetAll();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MprisPlayer::isValid() const { return this->player->isValid(); }
 | 
			
		||||
bool MprisPlayer::isReady() const { return this->mReady; }
 | 
			
		||||
 | 
			
		||||
void MprisPlayer::setPosition(QDBusObjectPath trackId, qlonglong position) { // NOLINT
 | 
			
		||||
	this->player->SetPosition(trackId, position);
 | 
			
		||||
}
 | 
			
		||||
void MprisPlayer::next() { this->player->Next(); }
 | 
			
		||||
void MprisPlayer::previous() { this->player->Previous(); }
 | 
			
		||||
void MprisPlayer::pause() { this->player->Pause(); }
 | 
			
		||||
void MprisPlayer::playPause() { this->player->PlayPause(); }
 | 
			
		||||
void MprisPlayer::stop() { this->player->Stop(); }
 | 
			
		||||
void MprisPlayer::play() { this->player->Play(); }
 | 
			
		||||
 | 
			
		||||
void MprisPlayer::onGetAllFinished() {
 | 
			
		||||
	if (this->mReady) return;
 | 
			
		||||
	this->mReady = true;
 | 
			
		||||
	emit this->ready();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MprisPlayer::updatePlayer() { // NOLINT
 | 
			
		||||
	                                 // TODO: emit signal here
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::mp
 | 
			
		||||
							
								
								
									
										67
									
								
								src/services/mpris/player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/services/mpris/player.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdbusextratypes.h>
 | 
			
		||||
#include <qdbuspendingcall.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/properties.hpp"
 | 
			
		||||
#include "dbus_player.h"
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_LOGGING_CATEGORY(logMprisPlayer);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::mp {
 | 
			
		||||
 | 
			
		||||
class MprisPlayer;
 | 
			
		||||
 | 
			
		||||
class MprisPlayer: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit MprisPlayer(const QString& address, QObject* parent = nullptr);	
 | 
			
		||||
	QString watcherId; // TODO: maybe can be private CHECK
 | 
			
		||||
 | 
			
		||||
	void setPosition(QDBusObjectPath trackId, qlonglong position);
 | 
			
		||||
	void next();
 | 
			
		||||
	void previous();
 | 
			
		||||
	void pause();
 | 
			
		||||
	void playPause();
 | 
			
		||||
 	void stop(); 
 | 
			
		||||
 	void play();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool isValid() const;
 | 
			
		||||
	[[nodiscard]] bool isReady() const;
 | 
			
		||||
	
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	dbus::DBusPropertyGroup properties;
 | 
			
		||||
	dbus::DBusProperty<bool> canControl {this->properties, "CanControl" };
 | 
			
		||||
	dbus::DBusProperty<bool> canGoNext {this->properties, "CanGoNext" };
 | 
			
		||||
	dbus::DBusProperty<bool> canGoPrevious {this->properties, "CanGoPrevious" };
 | 
			
		||||
	dbus::DBusProperty<bool> canPlay {this->properties, "CanPlay" };
 | 
			
		||||
	dbus::DBusProperty<bool> canPause {this->properties, "CanPause" };
 | 
			
		||||
	dbus::DBusProperty<QVariantMap> metadata {this->properties, "Metadata"};
 | 
			
		||||
	dbus::DBusProperty<QString> playbackStatus {this->properties, "PlaybackStatus" };	
 | 
			
		||||
	dbus::DBusProperty<qlonglong> position {this->properties, "Position" };
 | 
			
		||||
	dbus::DBusProperty<double> minimumRate {this->properties, "MinimumRate" };
 | 
			
		||||
	dbus::DBusProperty<double> maximumRate {this->properties, "MaximumRate" };
 | 
			
		||||
 | 
			
		||||
	dbus::DBusProperty<QString> loopStatus {this->properties, "LoopStatus" };
 | 
			
		||||
	dbus::DBusProperty<double> rate {this->properties, "Rate" };
 | 
			
		||||
	dbus::DBusProperty<bool> shuffle {this->properties, "Shuffle" };
 | 
			
		||||
	dbus::DBusProperty<double> volume {this->properties, "Volume" };
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void ready();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onGetAllFinished();
 | 
			
		||||
	void updatePlayer();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	DBusMprisPlayer* player = nullptr;
 | 
			
		||||
	bool mReady = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::mp
 | 
			
		||||
							
								
								
									
										180
									
								
								src/services/mpris/qml.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/services/mpris/qml.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,180 @@
 | 
			
		|||
#include "qml.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/properties.hpp"
 | 
			
		||||
#include "player.hpp"
 | 
			
		||||
#include "watcher.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace qs::dbus;
 | 
			
		||||
using namespace qs::service::mp;
 | 
			
		||||
 | 
			
		||||
Player::Player(qs::service::mp::MprisPlayer* player, QObject* parent)
 | 
			
		||||
    : QObject(parent)
 | 
			
		||||
    , player(player) {
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(&this->player->canControl, &AbstractDBusProperty::changed, this, &Player::canControlChanged);
 | 
			
		||||
	QObject::connect(&this->player->canGoNext, &AbstractDBusProperty::changed, this, &Player::canGoNextChanged);
 | 
			
		||||
	QObject::connect(&this->player->canGoPrevious, &AbstractDBusProperty::changed, this, &Player::canGoPreviousChanged);
 | 
			
		||||
	QObject::connect(&this->player->canPlay, &AbstractDBusProperty::changed, this, &Player::canPlayChanged);
 | 
			
		||||
	QObject::connect(&this->player->canPause, &AbstractDBusProperty::changed, this, &Player::canPauseChanged);
 | 
			
		||||
	QObject::connect(&this->player->metadata, &AbstractDBusProperty::changed, this, &Player::metadataChanged);
 | 
			
		||||
	QObject::connect(&this->player->playbackStatus, &AbstractDBusProperty::changed, this, &Player::playbackStatusChanged);	
 | 
			
		||||
	QObject::connect(&this->player->position, &AbstractDBusProperty::changed, this, &Player::positionChanged);
 | 
			
		||||
	QObject::connect(&this->player->minimumRate, &AbstractDBusProperty::changed, this, &Player::positionChanged);
 | 
			
		||||
	QObject::connect(&this->player->maximumRate, &AbstractDBusProperty::changed, this, &Player::positionChanged);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->player->loopStatus, &AbstractDBusProperty::changed, this, &Player::loopStatusChanged);
 | 
			
		||||
	QObject::connect(&this->player->rate, &AbstractDBusProperty::changed, this, &Player::rateChanged); 
 | 
			
		||||
	QObject::connect(&this->player->shuffle, &AbstractDBusProperty::changed, this, &Player::shuffleChanged); 
 | 
			
		||||
	QObject::connect(&this->player->volume, &AbstractDBusProperty::changed, this, &Player::volumeChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Player::canControl() const {
 | 
			
		||||
	if (this->player == nullptr) return false;
 | 
			
		||||
	return this->player->canControl.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Player::canGoNext() const {
 | 
			
		||||
	if (this->player == nullptr) return false;
 | 
			
		||||
	return this->player->canGoNext.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Player::canGoPrevious() const {
 | 
			
		||||
	if (this->player == nullptr) return false;
 | 
			
		||||
	return this->player->canGoPrevious.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Player::canPlay() const {
 | 
			
		||||
	if (this->player == nullptr) return false;
 | 
			
		||||
	return this->player->canPlay.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Player::canPause() const {
 | 
			
		||||
	if (this->player == nullptr) return false;
 | 
			
		||||
	return this->player->canPause.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVariantMap Player::metadata() const {
 | 
			
		||||
	if (this->player == nullptr) return {};
 | 
			
		||||
	return this->player->metadata.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString Player::playbackStatus() const {
 | 
			
		||||
	if (this->player == nullptr) return "";
 | 
			
		||||
 | 
			
		||||
	if (this->player->playbackStatus.get().isEmpty()) return "Unsupported";
 | 
			
		||||
	return this->player->playbackStatus.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qlonglong Player::position() const {
 | 
			
		||||
	if (this->player == nullptr) return 0;
 | 
			
		||||
	return this->player->position.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double Player::minimumRate() const {
 | 
			
		||||
	if (this->player == nullptr) return 0.0;
 | 
			
		||||
	return this->player->minimumRate.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double Player::maximumRate() const {
 | 
			
		||||
	if (this->player == nullptr) return 0.0;
 | 
			
		||||
	return this->player->maximumRate.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString Player::loopStatus() const {
 | 
			
		||||
	if (this->player == nullptr) return "";
 | 
			
		||||
 | 
			
		||||
	if (this->player->loopStatus.get().isEmpty()) return "Unsupported";
 | 
			
		||||
	return this->player->loopStatus.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double Player::rate() const {
 | 
			
		||||
	if (this->player == nullptr) return 0.0;
 | 
			
		||||
	return this->player->rate.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Player::shuffle() const {
 | 
			
		||||
	if (this->player == nullptr) return false;
 | 
			
		||||
	return this->player->shuffle.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double Player::volume() const {
 | 
			
		||||
	if (this->player == nullptr) return 0.0;
 | 
			
		||||
	return this->player->volume.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOLINTBEGIN
 | 
			
		||||
void Player::setPosition(QDBusObjectPath trackId, qlonglong position) const {
 | 
			
		||||
	this->player->setPosition(trackId, position);
 | 
			
		||||
}
 | 
			
		||||
void Player::next() const { this->player->next(); }
 | 
			
		||||
void Player::previous() const { this->player->previous(); }
 | 
			
		||||
void Player::pause() const { this->player->pause(); }
 | 
			
		||||
void Player::playPause() const { this->player->playPause(); }
 | 
			
		||||
void Player::stop() const { this->player->stop(); }
 | 
			
		||||
void Player::play() const { this->player->play(); }
 | 
			
		||||
// NOLINTEND
 | 
			
		||||
 | 
			
		||||
Mpris::Mpris(QObject* parent): QObject(parent) {
 | 
			
		||||
	auto* watcher = MprisWatcher::instance();
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(watcher, &MprisWatcher::MprisPlayerRegistered, this, &Mpris::onPlayerRegistered);
 | 
			
		||||
	QObject::connect(watcher, &MprisWatcher::MprisPlayerUnregistered, this, &Mpris::onPlayerUnregistered);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	for (QString& player: watcher->players) {
 | 
			
		||||
		this->mPlayers.push_back(new Player(new MprisPlayer(player), this));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Mpris::onPlayerRegistered(const QString& service) {
 | 
			
		||||
	this->mPlayers.push_back(new Player(new MprisPlayer(service), this));
 | 
			
		||||
	emit this->playersChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Mpris::onPlayerUnregistered(const QString& service) {
 | 
			
		||||
	Player* mprisPlayer = nullptr;
 | 
			
		||||
	MprisPlayer* player = playerWithAddress(players(), service)->player;
 | 
			
		||||
 | 
			
		||||
	this->mPlayers.removeIf([player, &mprisPlayer](Player* testPlayer) {
 | 
			
		||||
		if (testPlayer->player == player) {
 | 
			
		||||
			mprisPlayer = testPlayer;
 | 
			
		||||
			return true;
 | 
			
		||||
		} else return false;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	emit this->playersChanged();
 | 
			
		||||
 | 
			
		||||
	delete mprisPlayer->player;
 | 
			
		||||
	delete mprisPlayer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQmlListProperty<Player> Mpris::players() {
 | 
			
		||||
	return QQmlListProperty<Player>(this, nullptr, &Mpris::playersCount, &Mpris::playerAt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qsizetype Mpris::playersCount(QQmlListProperty<Player>* property) {
 | 
			
		||||
	return reinterpret_cast<Mpris*>(property->object)->mPlayers.count(); // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Player* Mpris::playerAt(QQmlListProperty<Player>* property, qsizetype index) {
 | 
			
		||||
	return reinterpret_cast<Mpris*>(property->object)->mPlayers.at(index); // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Player* Mpris::playerWithAddress(QQmlListProperty<Player> property, const QString& address) {
 | 
			
		||||
	for (Player* player: reinterpret_cast<Mpris*>(property.object)->mPlayers) { // NOLINT
 | 
			
		||||
		if (player->player->watcherId == address) {
 | 
			
		||||
			return player;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								src/services/mpris/qml.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/services/mpris/qml.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,113 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdbusextratypes.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "player.hpp"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
///! Mpris implementation for quickshell 
 | 
			
		||||
/// mpris service, get useful information from apps that implement media player fucntionality [mpris spec]
 | 
			
		||||
/// (Beware of misuse of spec, it is just a suggestion for most)
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
/// [mpris spec]: https://specifications.freedesktop.org/mpris-spec 
 | 
			
		||||
class Player: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// READ-ONLY 
 | 
			
		||||
	Q_PROPERTY(bool canControl READ canControl NOTIFY canControlChanged);
 | 
			
		||||
	Q_PROPERTY(bool canGoNext READ canGoNext NOTIFY canGoNextChanged);
 | 
			
		||||
	Q_PROPERTY(bool canGoPrevious READ canGoPrevious NOTIFY canGoPreviousChanged);
 | 
			
		||||
	Q_PROPERTY(bool canPlay READ canPlay NOTIFY canPlayChanged);
 | 
			
		||||
	Q_PROPERTY(bool canPause READ canPause NOTIFY canPauseChanged);	
 | 
			
		||||
	Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged);
 | 
			
		||||
	Q_PROPERTY(QString playbackStatus READ playbackStatus NOTIFY playbackStatusChanged);
 | 
			
		||||
	Q_PROPERTY(qlonglong position READ position NOTIFY positionChanged);
 | 
			
		||||
	Q_PROPERTY(double minimumRate READ minimumRate NOTIFY minimumRateChanged);
 | 
			
		||||
	Q_PROPERTY(double maximumRate READ maximumRate NOTIFY maximumRateChanged);
 | 
			
		||||
	
 | 
			
		||||
	// READ/WRITE - Write isn't implemented thus this need to fixed when that happens. 
 | 
			
		||||
	Q_PROPERTY(QString loopStatus READ loopStatus NOTIFY loopStatusChanged);
 | 
			
		||||
	Q_PROPERTY(double rate READ rate NOTIFY rateChanged);
 | 
			
		||||
	Q_PROPERTY(bool shuffle READ shuffle NOTIFY shuffleChanged);
 | 
			
		||||
	Q_PROPERTY(double volume READ volume NOTIFY volumeChanged);
 | 
			
		||||
	
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_UNCREATABLE("MprisPlayers can only be acquired from Mpris");
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit Player(qs::service::mp::MprisPlayer* player, QObject* parent = nullptr);
 | 
			
		||||
	
 | 
			
		||||
	// These are all self-explanatory.
 | 
			
		||||
	Q_INVOKABLE void setPosition(QDBusObjectPath trackId, qlonglong position) const; 
 | 
			
		||||
	Q_INVOKABLE void next() const; 
 | 
			
		||||
	Q_INVOKABLE void previous() const;
 | 
			
		||||
	Q_INVOKABLE void pause() const; 
 | 
			
		||||
	Q_INVOKABLE void playPause() const;
 | 
			
		||||
	Q_INVOKABLE void stop() const; 
 | 
			
		||||
	Q_INVOKABLE void play() const; 
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool canControl() const; 
 | 
			
		||||
	[[nodiscard]] bool canGoNext() const;
 | 
			
		||||
	[[nodiscard]] bool canGoPrevious() const; 
 | 
			
		||||
	[[nodiscard]] bool canPlay() const; 
 | 
			
		||||
	[[nodiscard]] bool canPause() const;
 | 
			
		||||
	[[nodiscard]] QVariantMap metadata() const; 
 | 
			
		||||
	[[nodiscard]] QString playbackStatus() const;	
 | 
			
		||||
	[[nodiscard]] qlonglong position() const;  
 | 
			
		||||
	[[nodiscard]] double minimumRate() const;
 | 
			
		||||
	[[nodiscard]] double maximumRate() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QString loopStatus() const; 
 | 
			
		||||
	[[nodiscard]] double rate() const; 
 | 
			
		||||
	[[nodiscard]] bool shuffle() const; 
 | 
			
		||||
	[[nodiscard]] double volume() const;
 | 
			
		||||
 | 
			
		||||
	qs::service::mp::MprisPlayer* player = nullptr;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void canControlChanged();
 | 
			
		||||
	void canGoNextChanged(); 
 | 
			
		||||
	void canGoPreviousChanged();
 | 
			
		||||
	void canPlayChanged();
 | 
			
		||||
	void canPauseChanged();
 | 
			
		||||
	void metadataChanged();
 | 
			
		||||
	void playbackStatusChanged();	
 | 
			
		||||
	void positionChanged();
 | 
			
		||||
	void minimumRateChanged();
 | 
			
		||||
	void maximumRateChanged();
 | 
			
		||||
 | 
			
		||||
	void loopStatusChanged();
 | 
			
		||||
	void rateChanged();
 | 
			
		||||
	void shuffleChanged();
 | 
			
		||||
	void volumeChanged();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Mpris: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	Q_PROPERTY(QQmlListProperty<Player> players READ players NOTIFY playersChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_SINGLETON;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit Mpris(QObject* parent = nullptr);
 | 
			
		||||
	
 | 
			
		||||
	[[nodiscard]] QQmlListProperty<Player> players();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void playersChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onPlayerRegistered(const QString& service);
 | 
			
		||||
	void onPlayerUnregistered(const QString& service);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static qsizetype playersCount(QQmlListProperty<Player>* property);
 | 
			
		||||
	static Player* playerAt(QQmlListProperty<Player>* property, qsizetype index);
 | 
			
		||||
	static Player* playerWithAddress(QQmlListProperty<Player> property, const QString& address);  
 | 
			
		||||
 | 
			
		||||
	QList<Player*> mPlayers;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										146
									
								
								src/services/mpris/watcher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/services/mpris/watcher.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,146 @@
 | 
			
		|||
#include "watcher.hpp"
 | 
			
		||||
 | 
			
		||||
#include <dbus_watcher.h>
 | 
			
		||||
#include <qdbusconnection.h>
 | 
			
		||||
#include <qdbusconnectioninterface.h>
 | 
			
		||||
#include <qdbusservicewatcher.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logMprisWatcher, "quickshell.service.mp.watcher", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::mp {
 | 
			
		||||
 | 
			
		||||
MprisWatcher::MprisWatcher(QObject* parent): QObject(parent) {
 | 
			
		||||
	new MprisWatcherAdaptor(this);
 | 
			
		||||
 | 
			
		||||
	qCDebug(logMprisWatcher) << "Starting MprisWatcher";
 | 
			
		||||
 | 
			
		||||
	auto bus = QDBusConnection::sessionBus();
 | 
			
		||||
 | 
			
		||||
	if (!bus.isConnected()) {
 | 
			
		||||
		qCWarning(logMprisWatcher) << "Could not connect to DBus. Mpris service will not work.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!bus.registerObject("/MprisWatcher", this)) {
 | 
			
		||||
		qCWarning(logMprisWatcher) << "Could not register MprisWatcher object with "
 | 
			
		||||
		                              "DBus. Mpris service will not work.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(&this->serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &MprisWatcher::onServiceRegistered);
 | 
			
		||||
	QObject::connect(&this->serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &MprisWatcher::onServiceUnregistered);
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.addWatchedService("org.mpris.MediaPlayer2*");
 | 
			
		||||
	this->serviceWatcher.addWatchedService("org.mpris.MprisWatcher");
 | 
			
		||||
	this->serviceWatcher.setConnection(bus);
 | 
			
		||||
 | 
			
		||||
	this->tryRegister();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MprisWatcher::tryRegister() { // NOLINT
 | 
			
		||||
	auto bus = QDBusConnection::sessionBus();
 | 
			
		||||
	auto success = bus.registerService("org.mpris.MprisWatcher");
 | 
			
		||||
 | 
			
		||||
	if (success) {
 | 
			
		||||
		qCDebug(logMprisWatcher) << "Registered watcher at org.mpris.MprisWatcher";
 | 
			
		||||
		emit this->MprisWatcherRegistered();
 | 
			
		||||
		registerExisting(bus); // Register services that already existed before creation.
 | 
			
		||||
	} else {
 | 
			
		||||
		qCDebug(logMprisWatcher) << "Could not register watcher at "
 | 
			
		||||
		                            "org.mpris.MprisWatcher, presumably because one is "
 | 
			
		||||
		                            "already registered.";
 | 
			
		||||
		qCDebug(logMprisWatcher
 | 
			
		||||
		) << "Registration will be attempted again if the active service is unregistered.";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MprisWatcher::registerExisting(const QDBusConnection& connection) {
 | 
			
		||||
	QStringList list = connection.interface()->registeredServiceNames();
 | 
			
		||||
	for (const QString& service: list) {
 | 
			
		||||
		if (service.contains("org.mpris.MediaPlayer2")) {
 | 
			
		||||
			qCDebug(logMprisWatcher).noquote() << "Found Mpris service" << service;
 | 
			
		||||
			RegisterMprisPlayer(service);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MprisWatcher::onServiceRegistered(const QString& service) {
 | 
			
		||||
	if (service == "org.mpris.MprisWatcher") {
 | 
			
		||||
		qCDebug(logMprisWatcher) << "MprisWatcher";
 | 
			
		||||
		return;
 | 
			
		||||
	} else if (service.contains("org.mpris.MediaPlayer2")) {
 | 
			
		||||
		qCDebug(logMprisWatcher).noquote() << "Mpris service " << service << " registered.";
 | 
			
		||||
		RegisterMprisPlayer(service);
 | 
			
		||||
	} else {
 | 
			
		||||
		qCWarning(logMprisWatcher) << "Got a registration event for a untracked service";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: This is getting triggered twice on unregistration, investigate.
 | 
			
		||||
void MprisWatcher::onServiceUnregistered(const QString& service) {
 | 
			
		||||
	if (service == "org.mpris.MprisWatcher") {
 | 
			
		||||
		qCDebug(logMprisWatcher) << "Active MprisWatcher unregistered, attempting registration";
 | 
			
		||||
		this->tryRegister();
 | 
			
		||||
		return;
 | 
			
		||||
	} else {
 | 
			
		||||
		QString qualifiedPlayer;
 | 
			
		||||
		this->players.removeIf([&](const QString& player) {
 | 
			
		||||
			if (QString::compare(player, service) == 0) {
 | 
			
		||||
				qualifiedPlayer = player;
 | 
			
		||||
				return true;
 | 
			
		||||
			} else return false;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (!qualifiedPlayer.isEmpty()) {
 | 
			
		||||
			qCDebug(logMprisWatcher).noquote()
 | 
			
		||||
			    << "Unregistered MprisPlayer" << qualifiedPlayer << "from watcher";
 | 
			
		||||
 | 
			
		||||
			emit this->MprisPlayerUnregistered(qualifiedPlayer);
 | 
			
		||||
		} else {
 | 
			
		||||
			qCWarning(logMprisWatcher).noquote()
 | 
			
		||||
			    << "Got service unregister event for untracked service" << service;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.removeWatchedService(service);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QList<QString> MprisWatcher::registeredPlayers() const { return this->players; }
 | 
			
		||||
 | 
			
		||||
void MprisWatcher::RegisterMprisPlayer(const QString& player) {
 | 
			
		||||
	if (this->players.contains(player)) {
 | 
			
		||||
		qCDebug(logMprisWatcher).noquote()
 | 
			
		||||
		    << "Skipping duplicate registration of MprisPlayer" << player << "to watcher";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!QDBusConnection::sessionBus().interface()->serviceOwner(player).isValid()) {
 | 
			
		||||
		qCWarning(logMprisWatcher).noquote()
 | 
			
		||||
		    << "Ignoring invalid MprisPlayer registration of" << player << "to watcher";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.addWatchedService(player);
 | 
			
		||||
	this->players.push_back(player);
 | 
			
		||||
 | 
			
		||||
	qCDebug(logMprisWatcher).noquote() << "Registered MprisPlayer" << player << "to watcher";
 | 
			
		||||
 | 
			
		||||
	emit this->MprisPlayerRegistered(player);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MprisWatcher* MprisWatcher::instance() {
 | 
			
		||||
	static MprisWatcher* instance = nullptr; // NOLINT
 | 
			
		||||
	if (instance == nullptr) instance = new MprisWatcher();
 | 
			
		||||
	return instance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::mp
 | 
			
		||||
							
								
								
									
										53
									
								
								src/services/mpris/watcher.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/services/mpris/watcher.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdbuscontext.h>
 | 
			
		||||
#include <qdbusinterface.h>
 | 
			
		||||
#include <qdbusservicewatcher.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_LOGGING_CATEGORY(logMprisWatcher);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::mp {
 | 
			
		||||
 | 
			
		||||
class MprisWatcher
 | 
			
		||||
    : public QObject
 | 
			
		||||
    , protected QDBusContext {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	Q_PROPERTY(qint32 ProtocolVersion READ protocolVersion);
 | 
			
		||||
	Q_PROPERTY(QList<QString> RegisteredMprisPlayers READ registeredPlayers);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit MprisWatcher(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	void tryRegister();
 | 
			
		||||
	void registerExisting(const QDBusConnection &connection); 
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] qint32 protocolVersion() const { return 0; } // NOLINT
 | 
			
		||||
	[[nodiscard]] QList<QString> registeredPlayers() const;
 | 
			
		||||
 | 
			
		||||
	// NOLINTBEGIN
 | 
			
		||||
	void RegisterMprisPlayer(const QString& player);
 | 
			
		||||
	// NOLINTEND
 | 
			
		||||
 | 
			
		||||
	static MprisWatcher* instance();
 | 
			
		||||
	QList<QString> players;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	// NOLINTBEGIN
 | 
			
		||||
	void MprisWatcherRegistered();
 | 
			
		||||
	void MprisPlayerRegistered(const QString& service);
 | 
			
		||||
	void MprisPlayerUnregistered(const QString& service);	
 | 
			
		||||
	// NOLINTEND
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onServiceRegistered(const QString& service); 
 | 
			
		||||
	void onServiceUnregistered(const QString& service);
 | 
			
		||||
 | 
			
		||||
private:	
 | 
			
		||||
	QDBusServiceWatcher serviceWatcher;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::mp
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue