forked from quickshell/quickshell
		
	service/mpris: add mpris module
This commit is contained in:
		
						commit
						af45502913
					
				
					 14 changed files with 1101 additions and 20 deletions
				
			
		| 
						 | 
					@ -20,6 +20,7 @@ option(HYPRLAND_GLOBAL_SHORTCUTS "Hyprland Global Shortcuts" ON)
 | 
				
			||||||
option(HYPRLAND_FOCUS_GRAB "Hyprland Focus Grabbing" ON)
 | 
					option(HYPRLAND_FOCUS_GRAB "Hyprland Focus Grabbing" ON)
 | 
				
			||||||
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
					option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
				
			||||||
option(SERVICE_PIPEWIRE "PipeWire service" ON)
 | 
					option(SERVICE_PIPEWIRE "PipeWire service" ON)
 | 
				
			||||||
 | 
					option(SERVICE_MPRIS "Mpris service" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message(STATUS "Quickshell configuration")
 | 
					message(STATUS "Quickshell configuration")
 | 
				
			||||||
message(STATUS "  NVIDIA workarounds: ${NVIDIA_COMPAT}")
 | 
					message(STATUS "  NVIDIA workarounds: ${NVIDIA_COMPAT}")
 | 
				
			||||||
| 
						 | 
					@ -34,6 +35,7 @@ message(STATUS "  X11: ${X11}")
 | 
				
			||||||
message(STATUS "  Services")
 | 
					message(STATUS "  Services")
 | 
				
			||||||
message(STATUS "    StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
 | 
					message(STATUS "    StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
 | 
				
			||||||
message(STATUS "    PipeWire: ${SERVICE_PIPEWIRE}")
 | 
					message(STATUS "    PipeWire: ${SERVICE_PIPEWIRE}")
 | 
				
			||||||
 | 
					message(STATUS "    Mpris: ${SERVICE_MPRIS}")
 | 
				
			||||||
message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
					message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
				
			||||||
if (HYPRLAND)
 | 
					if (HYPRLAND)
 | 
				
			||||||
	message(STATUS "    Focus Grabbing: ${HYPRLAND_FOCUS_GRAB}")
 | 
						message(STATUS "    Focus Grabbing: ${HYPRLAND_FOCUS_GRAB}")
 | 
				
			||||||
| 
						 | 
					@ -89,7 +91,7 @@ if (WAYLAND)
 | 
				
			||||||
	list(APPEND QT_FPDEPS WaylandClient)
 | 
						list(APPEND QT_FPDEPS WaylandClient)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (SERVICE_STATUS_NOTIFIER)
 | 
					if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS)
 | 
				
			||||||
	set(DBUS ON)
 | 
						set(DBUS ON)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -188,9 +188,9 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dbus::DBusPropertyGroup properties;
 | 
						dbus::DBusPropertyGroup properties;
 | 
				
			||||||
	dbus::DBusProperty<quint32> version {this->properties, "Version"};
 | 
						dbus::DBusProperty<quint32> version {this->properties, "Version"};
 | 
				
			||||||
	dbus::DBusProperty<QString> textDirection {this->properties, "TextDirection"};
 | 
						dbus::DBusProperty<QString> textDirection {this->properties, "TextDirection", "", false};
 | 
				
			||||||
	dbus::DBusProperty<QString> status {this->properties, "Status"};
 | 
						dbus::DBusProperty<QString> status {this->properties, "Status"};
 | 
				
			||||||
	dbus::DBusProperty<QStringList> iconThemePath {this->properties, "IconThemePath"};
 | 
						dbus::DBusProperty<QStringList> iconThemePath {this->properties, "IconThemePath", {}, false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void prepareToShow(qint32 item, bool sendOpened);
 | 
						void prepareToShow(qint32 item, bool sendOpened);
 | 
				
			||||||
	void updateLayout(qint32 parent, qint32 depth);
 | 
						void updateLayout(qint32 parent, qint32 depth);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,6 +112,8 @@ void asyncReadPropertyInternal(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AbstractDBusProperty::tryUpdate(const QVariant& variant) {
 | 
					void AbstractDBusProperty::tryUpdate(const QVariant& variant) {
 | 
				
			||||||
 | 
						this->mExists = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto error = this->read(variant);
 | 
						auto error = this->read(variant);
 | 
				
			||||||
	if (error.isValid()) {
 | 
						if (error.isValid()) {
 | 
				
			||||||
		qCWarning(logDbusProperties).noquote()
 | 
							qCWarning(logDbusProperties).noquote()
 | 
				
			||||||
| 
						 | 
					@ -159,6 +161,44 @@ void AbstractDBusProperty::update() {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AbstractDBusProperty::write() {
 | 
				
			||||||
 | 
						if (this->group == nullptr) {
 | 
				
			||||||
 | 
							qFatal(logDbusProperties) << "Tried to write dbus property" << this->name
 | 
				
			||||||
 | 
							                          << "which is not attached to a group";
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const QString propStr = this->toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this->group->interface == nullptr) {
 | 
				
			||||||
 | 
								qFatal(logDbusProperties).noquote()
 | 
				
			||||||
 | 
								    << "Tried to write property" << propStr << "of a disconnected interface";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logDbusProperties).noquote() << "Writing property" << propStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto pendingCall = this->group->propertyInterface->Set(
 | 
				
			||||||
 | 
							    this->group->interface->interface(),
 | 
				
			||||||
 | 
							    this->name,
 | 
				
			||||||
 | 
							    QDBusVariant(this->serialize())
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* call = new QDBusPendingCallWatcher(pendingCall, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto responseCallback = [propStr](QDBusPendingCallWatcher* call) {
 | 
				
			||||||
 | 
								const QDBusPendingReply<> reply = *call;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (reply.isError()) {
 | 
				
			||||||
 | 
									qCWarning(logDbusProperties).noquote() << "Error writing property" << propStr;
 | 
				
			||||||
 | 
									qCWarning(logDbusProperties) << reply.error();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								delete call;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AbstractDBusProperty::exists() const { return this->mExists; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString AbstractDBusProperty::toString() const {
 | 
					QString AbstractDBusProperty::toString() const {
 | 
				
			||||||
	const QString group = this->group == nullptr ? "{ NO GROUP }" : this->group->toString();
 | 
						const QString group = this->group == nullptr ? "{ NO GROUP }" : this->group->toString();
 | 
				
			||||||
	return group + ':' + this->name;
 | 
						return group + ':' + this->name;
 | 
				
			||||||
| 
						 | 
					@ -232,7 +272,7 @@ void DBusPropertyGroup::updateAllViaGetAll() {
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			qCDebug(logDbusProperties).noquote()
 | 
								qCDebug(logDbusProperties).noquote()
 | 
				
			||||||
			    << "Received GetAll property set for" << this->toString();
 | 
								    << "Received GetAll property set for" << this->toString();
 | 
				
			||||||
			this->updatePropertySet(reply.value());
 | 
								this->updatePropertySet(reply.value(), true);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		delete call;
 | 
							delete call;
 | 
				
			||||||
| 
						 | 
					@ -242,7 +282,7 @@ void DBusPropertyGroup::updateAllViaGetAll() {
 | 
				
			||||||
	QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
 | 
						QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties) {
 | 
					void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool complainMissing) {
 | 
				
			||||||
	for (const auto [name, value]: properties.asKeyValueRange()) {
 | 
						for (const auto [name, value]: properties.asKeyValueRange()) {
 | 
				
			||||||
		auto prop = std::find_if(
 | 
							auto prop = std::find_if(
 | 
				
			||||||
		    this->properties.begin(),
 | 
							    this->properties.begin(),
 | 
				
			||||||
| 
						 | 
					@ -251,11 +291,21 @@ void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties) {
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (prop == this->properties.end()) {
 | 
							if (prop == this->properties.end()) {
 | 
				
			||||||
			qCDebug(logDbusProperties) << "Ignoring untracked property update" << name << "for" << this;
 | 
								qCDebug(logDbusProperties) << "Ignoring untracked property update" << name << "for"
 | 
				
			||||||
 | 
								                           << this->toString();
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			(*prop)->tryUpdate(value);
 | 
								(*prop)->tryUpdate(value);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (complainMissing) {
 | 
				
			||||||
 | 
							for (const auto* prop: this->properties) {
 | 
				
			||||||
 | 
								if (prop->required && !properties.contains(prop->name)) {
 | 
				
			||||||
 | 
									qCWarning(logDbusProperties)
 | 
				
			||||||
 | 
									    << prop->name << "missing from property set for" << this->toString();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString DBusPropertyGroup::toString() const {
 | 
					QString DBusPropertyGroup::toString() const {
 | 
				
			||||||
| 
						 | 
					@ -291,7 +341,7 @@ void DBusPropertyGroup::onPropertiesChanged(
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->updatePropertySet(changedProperties);
 | 
						this->updatePropertySet(changedProperties, false);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace qs::dbus
 | 
					} // namespace qs::dbus
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,22 +79,31 @@ class AbstractDBusProperty: public QObject {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit AbstractDBusProperty(QString name, const QMetaType& type, QObject* parent = nullptr)
 | 
						explicit AbstractDBusProperty(
 | 
				
			||||||
 | 
						    QString name,
 | 
				
			||||||
 | 
						    const QMetaType& type,
 | 
				
			||||||
 | 
						    bool required,
 | 
				
			||||||
 | 
						    QObject* parent = nullptr
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
	    : QObject(parent)
 | 
						    : QObject(parent)
 | 
				
			||||||
	    , name(std::move(name))
 | 
						    , name(std::move(name))
 | 
				
			||||||
	    , type(type) {}
 | 
						    , type(type)
 | 
				
			||||||
 | 
						    , required(required) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool exists() const;
 | 
				
			||||||
	[[nodiscard]] QString toString() const;
 | 
						[[nodiscard]] QString toString() const;
 | 
				
			||||||
	[[nodiscard]] virtual QString valueString() = 0;
 | 
						[[nodiscard]] virtual QString valueString() = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public slots:
 | 
					public slots:
 | 
				
			||||||
	void update();
 | 
						void update();
 | 
				
			||||||
 | 
						void write();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void changed();
 | 
						void changed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protected:
 | 
					protected:
 | 
				
			||||||
	virtual QDBusError read(const QVariant& variant) = 0;
 | 
						virtual QDBusError read(const QVariant& variant) = 0;
 | 
				
			||||||
 | 
						virtual QVariant serialize() = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	void tryUpdate(const QVariant& variant);
 | 
						void tryUpdate(const QVariant& variant);
 | 
				
			||||||
| 
						 | 
					@ -103,6 +112,8 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString name;
 | 
						QString name;
 | 
				
			||||||
	QMetaType type;
 | 
						QMetaType type;
 | 
				
			||||||
 | 
						bool required;
 | 
				
			||||||
 | 
						bool mExists = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	friend class DBusPropertyGroup;
 | 
						friend class DBusPropertyGroup;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -133,7 +144,7 @@ private slots:
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	void updatePropertySet(const QVariantMap& properties);
 | 
						void updatePropertySet(const QVariantMap& properties, bool complainMissing);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	DBusPropertiesInterface* propertyInterface = nullptr;
 | 
						DBusPropertiesInterface* propertyInterface = nullptr;
 | 
				
			||||||
	QDBusAbstractInterface* interface = nullptr;
 | 
						QDBusAbstractInterface* interface = nullptr;
 | 
				
			||||||
| 
						 | 
					@ -145,17 +156,23 @@ private:
 | 
				
			||||||
template <typename T>
 | 
					template <typename T>
 | 
				
			||||||
class DBusProperty: public AbstractDBusProperty {
 | 
					class DBusProperty: public AbstractDBusProperty {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit DBusProperty(QString name, QObject* parent = nullptr, T value = T())
 | 
						explicit DBusProperty(
 | 
				
			||||||
	    : AbstractDBusProperty(std::move(name), QMetaType::fromType<T>(), parent)
 | 
						    QString name,
 | 
				
			||||||
 | 
						    T value = T(),
 | 
				
			||||||
 | 
						    bool required = true,
 | 
				
			||||||
 | 
						    QObject* parent = nullptr
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						    : AbstractDBusProperty(std::move(name), QMetaType::fromType<T>(), required, parent)
 | 
				
			||||||
	    , value(std::move(value)) {}
 | 
						    , value(std::move(value)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	explicit DBusProperty(
 | 
						explicit DBusProperty(
 | 
				
			||||||
	    DBusPropertyGroup& group,
 | 
						    DBusPropertyGroup& group,
 | 
				
			||||||
	    QString name,
 | 
						    QString name,
 | 
				
			||||||
	    QObject* parent = nullptr,
 | 
						    T value = T(),
 | 
				
			||||||
	    T value = T()
 | 
						    bool required = true,
 | 
				
			||||||
 | 
						    QObject* parent = nullptr
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	    : DBusProperty(std::move(name), parent, std::move(value)) {
 | 
						    : DBusProperty(std::move(name), std::move(value), required, parent) {
 | 
				
			||||||
		group.attachProperty(this);
 | 
							group.attachProperty(this);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,7 +182,7 @@ public:
 | 
				
			||||||
		return str;
 | 
							return str;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] T get() const { return this->value; }
 | 
						[[nodiscard]] const T& get() const { return this->value; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void set(T value) {
 | 
						void set(T value) {
 | 
				
			||||||
		this->value = std::move(value);
 | 
							this->value = std::move(value);
 | 
				
			||||||
| 
						 | 
					@ -183,6 +200,8 @@ protected:
 | 
				
			||||||
		return result.error;
 | 
							return result.error;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QVariant serialize() override { return QVariant::fromValue(this->value); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	T value;
 | 
						T value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,7 @@ endif()
 | 
				
			||||||
if (SERVICE_PIPEWIRE)
 | 
					if (SERVICE_PIPEWIRE)
 | 
				
			||||||
	add_subdirectory(pipewire)
 | 
						add_subdirectory(pipewire)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (SERVICE_MPRIS)
 | 
				
			||||||
 | 
						add_subdirectory(mpris)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/services/mpris/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/services/mpris/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					set_source_files_properties(org.mpris.MediaPlayer2.Player.xml PROPERTIES
 | 
				
			||||||
 | 
						CLASSNAME DBusMprisPlayer
 | 
				
			||||||
 | 
						NO_NAMESPACE TRUE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_dbus_interface(DBUS_INTERFACES
 | 
				
			||||||
 | 
						org.mpris.MediaPlayer2.Player.xml
 | 
				
			||||||
 | 
						dbus_player
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set_source_files_properties(org.mpris.MediaPlayer2.xml PROPERTIES
 | 
				
			||||||
 | 
						CLASSNAME DBusMprisPlayerApp
 | 
				
			||||||
 | 
						NO_NAMESPACE TRUE
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_dbus_interface(DBUS_INTERFACES
 | 
				
			||||||
 | 
						org.mpris.MediaPlayer2.xml
 | 
				
			||||||
 | 
						dbus_player_app
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_library(quickshell-service-mpris STATIC
 | 
				
			||||||
 | 
						player.cpp
 | 
				
			||||||
 | 
						watcher.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)
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/services/mpris/module.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/services/mpris/module.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					name = "Quickshell.Services.Mpris"
 | 
				
			||||||
 | 
					description = "Mpris Service"
 | 
				
			||||||
 | 
					headers = [
 | 
				
			||||||
 | 
						"player.hpp",
 | 
				
			||||||
 | 
						"watcher.hpp",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/services/mpris/org.mpris.MediaPlayer2.Player.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/services/mpris/org.mpris.MediaPlayer2.Player.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					<node>
 | 
				
			||||||
 | 
						<interface name="org.mpris.MediaPlayer2.Player">
 | 
				
			||||||
 | 
							<method name="OpenUri">
 | 
				
			||||||
 | 
								<arg direction="in" type="s" name="Uri"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
							<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"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<signal name="Seeked">
 | 
				
			||||||
 | 
								<arg type="x" name="Position"/>
 | 
				
			||||||
 | 
							</signal>
 | 
				
			||||||
 | 
					  </interface>
 | 
				
			||||||
 | 
					</node>
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/services/mpris/org.mpris.MediaPlayer2.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/services/mpris/org.mpris.MediaPlayer2.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					<node>
 | 
				
			||||||
 | 
						<interface name="org.mpris.MediaPlayer2">
 | 
				
			||||||
 | 
							<method name="Raise"/>
 | 
				
			||||||
 | 
							<method name="Quit"/>
 | 
				
			||||||
 | 
						</interface>
 | 
				
			||||||
 | 
					</node>
 | 
				
			||||||
							
								
								
									
										427
									
								
								src/services/mpris/player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								src/services/mpris/player.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,427 @@
 | 
				
			||||||
 | 
					#include "player.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdatetime.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbusextratypes.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../dbus/properties.hpp"
 | 
				
			||||||
 | 
					#include "dbus_player.h"
 | 
				
			||||||
 | 
					#include "dbus_player_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace qs::dbus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::mpris {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logMprisPlayer, "quickshell.service.mp.player", QtWarningMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString MprisPlaybackState::toString(MprisPlaybackState::Enum status) {
 | 
				
			||||||
 | 
						switch (status) {
 | 
				
			||||||
 | 
						case MprisPlaybackState::Stopped: return "Stopped";
 | 
				
			||||||
 | 
						case MprisPlaybackState::Playing: return "Playing";
 | 
				
			||||||
 | 
						case MprisPlaybackState::Paused: return "Paused";
 | 
				
			||||||
 | 
						default: return "Unknown Status";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString MprisLoopState::toString(MprisLoopState::Enum status) {
 | 
				
			||||||
 | 
						switch (status) {
 | 
				
			||||||
 | 
						case MprisLoopState::None: return "None";
 | 
				
			||||||
 | 
						case MprisLoopState::Track: return "Track";
 | 
				
			||||||
 | 
						case MprisLoopState::Playlist: return "Playlist";
 | 
				
			||||||
 | 
						default: return "Unknown Status";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MprisPlayer::MprisPlayer(const QString& address, QObject* parent): QObject(parent) {
 | 
				
			||||||
 | 
						this->app = new DBusMprisPlayerApp(
 | 
				
			||||||
 | 
						    address,
 | 
				
			||||||
 | 
						    "/org/mpris/MediaPlayer2",
 | 
				
			||||||
 | 
						    QDBusConnection::sessionBus(),
 | 
				
			||||||
 | 
						    this
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->player =
 | 
				
			||||||
 | 
						    new DBusMprisPlayer(address, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus(), this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->player->isValid() || !this->app->isValid()) {
 | 
				
			||||||
 | 
							qCWarning(logMprisPlayer) << "Cannot create MprisPlayer for" << address;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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->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->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.
 | 
				
			||||||
 | 
						QObject::connect(this, &MprisPlayer::positionChanged, this, &MprisPlayer::onExportedPositionChanged);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->appProperties.setInterface(this->app);
 | 
				
			||||||
 | 
						this->playerProperties.setInterface(this->player);
 | 
				
			||||||
 | 
						this->appProperties.updateAllViaGetAll();
 | 
				
			||||||
 | 
						this->playerProperties.updateAllViaGetAll();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::raise() {
 | 
				
			||||||
 | 
						if (!this->canRaise()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot call raise() on" << this << "because canRaise is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->app->Raise();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::quit() {
 | 
				
			||||||
 | 
						if (!this->canQuit()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot call quit() on" << this << "because canQuit is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->app->Quit();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::openUri(const QString& uri) { this->player->OpenUri(uri); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::next() {
 | 
				
			||||||
 | 
						if (!this->canGoNext()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot call next() on" << this << "because canGoNext is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->player->Next();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::previous() {
 | 
				
			||||||
 | 
						if (!this->canGoPrevious()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot call previous() on" << this << "because canGoPrevious is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->player->Previous();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::seek(qreal offset) {
 | 
				
			||||||
 | 
						if (!this->canSeek()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot call seek() on" << this << "because canSeek is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto target = static_cast<qlonglong>(offset * 1000) * 1000;
 | 
				
			||||||
 | 
						this->player->Seek(target);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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::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(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString MprisPlayer::identity() const { return this->pIdentity.get(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qlonglong MprisPlayer::positionMs() const {
 | 
				
			||||||
 | 
						if (!this->positionSupported()) return 0; // unsupported
 | 
				
			||||||
 | 
						if (this->mPlaybackState == MprisPlaybackState::Stopped) return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto paused = this->mPlaybackState == MprisPlaybackState::Paused;
 | 
				
			||||||
 | 
						auto time = paused ? this->pausedTime : QDateTime::currentDateTime();
 | 
				
			||||||
 | 
						auto offset = time - this->lastPositionTimestamp;
 | 
				
			||||||
 | 
						auto rateMul = static_cast<qlonglong>(this->pRate.get() * 1000);
 | 
				
			||||||
 | 
						offset = (offset * rateMul) / 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return (this->pPosition.get() / 1000) + offset.count();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qreal MprisPlayer::position() const {
 | 
				
			||||||
 | 
						if (!this->positionSupported()) return 0; // unsupported
 | 
				
			||||||
 | 
						if (this->mPlaybackState == MprisPlaybackState::Stopped) return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return static_cast<qreal>(this->positionMs()) / 1000.0; // NOLINT
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool MprisPlayer::positionSupported() const { return this->pPosition.exists(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::setPosition(qreal position) {
 | 
				
			||||||
 | 
						if (this->pPosition.get() == -1) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set position of" << this << "because position is not supported.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->canSeek()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set position of" << this << "because canSeek is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto target = static_cast<qlonglong>(position * 1000) * 1000;
 | 
				
			||||||
 | 
						this->pPosition.set(target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->mTrackId.isEmpty()) {
 | 
				
			||||||
 | 
							this->player->SetPosition(QDBusObjectPath(this->mTrackId), target);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							auto pos = this->positionMs() * 1000;
 | 
				
			||||||
 | 
							this->player->Seek(target - pos);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::onPositionChanged() {
 | 
				
			||||||
 | 
						const bool firstChange = !this->lastPositionTimestamp.isValid();
 | 
				
			||||||
 | 
						this->lastPositionTimestamp = QDateTime::currentDateTimeUtc();
 | 
				
			||||||
 | 
						emit this->positionChanged();
 | 
				
			||||||
 | 
						if (firstChange) emit this->positionSupportedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::onExportedPositionChanged() {
 | 
				
			||||||
 | 
						if (!this->lengthSupported()) emit this->lengthChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::onSeek(qlonglong time) { this->pPosition.set(time); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qreal MprisPlayer::length() const {
 | 
				
			||||||
 | 
						if (this->mLength == -1) {
 | 
				
			||||||
 | 
							return this->position(); // unsupported
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return static_cast<qreal>(this->mLength / 1000) / 1000; // NOLINT
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool MprisPlayer::lengthSupported() const { return this->mLength != -1; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qreal MprisPlayer::volume() const { return this->pVolume.get(); }
 | 
				
			||||||
 | 
					bool MprisPlayer::volumeSupported() const { return this->pVolume.exists(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::setVolume(qreal volume) {
 | 
				
			||||||
 | 
						if (!this->canControl()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set volume of" << this << "because canControl is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->volumeSupported()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set volume of" << this << "because volume is not supported.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->pVolume.set(volume);
 | 
				
			||||||
 | 
						this->pVolume.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVariantMap MprisPlayer::metadata() const { return this->pMetadata.get(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::onMetadataChanged() {
 | 
				
			||||||
 | 
						auto lengthVariant = this->pMetadata.get().value("mpris:length");
 | 
				
			||||||
 | 
						qlonglong length = -1;
 | 
				
			||||||
 | 
						if (lengthVariant.isValid() && lengthVariant.canConvert<qlonglong>()) {
 | 
				
			||||||
 | 
							length = lengthVariant.value<qlonglong>();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (length != this->mLength) {
 | 
				
			||||||
 | 
							this->mLength = length;
 | 
				
			||||||
 | 
							emit this->lengthChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto trackidVariant = this->pMetadata.get().value("mpris:trackid");
 | 
				
			||||||
 | 
						if (trackidVariant.isValid() && trackidVariant.canConvert<QString>()) {
 | 
				
			||||||
 | 
							this->mTrackId = trackidVariant.value<QString>();
 | 
				
			||||||
 | 
							this->onSeek(0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->metadataChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MprisPlaybackState::Enum MprisPlayer::playbackState() const { return this->mPlaybackState; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::setPlaybackState(MprisPlaybackState::Enum playbackState) {
 | 
				
			||||||
 | 
						if (playbackState == this->mPlaybackState) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (playbackState) {
 | 
				
			||||||
 | 
						case MprisPlaybackState::Stopped:
 | 
				
			||||||
 | 
							if (!this->canControl()) {
 | 
				
			||||||
 | 
								qWarning() << "Cannot set playbackState of" << this
 | 
				
			||||||
 | 
								           << "to Stopped because canControl is false.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->player->Stop();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case MprisPlaybackState::Playing:
 | 
				
			||||||
 | 
							if (!this->canPlay()) {
 | 
				
			||||||
 | 
								qWarning() << "Cannot set playbackState of" << this << "to Playing because canPlay is false.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->player->Play();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case MprisPlaybackState::Paused:
 | 
				
			||||||
 | 
							if (!this->canPause()) {
 | 
				
			||||||
 | 
								qWarning() << "Cannot set playbackState of" << this << "to Paused because canPause is false.";
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->player->Pause();
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							qWarning() << "Cannot set playbackState of" << this << "to unknown value" << playbackState;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisPlayer::onPlaybackStatusChanged() {
 | 
				
			||||||
 | 
						const auto& status = this->pPlaybackStatus.get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (status == "Playing") {
 | 
				
			||||||
 | 
							this->mPlaybackState = MprisPlaybackState::Playing;
 | 
				
			||||||
 | 
						} else if (status == "Paused") {
 | 
				
			||||||
 | 
							this->mPlaybackState = MprisPlaybackState::Paused;
 | 
				
			||||||
 | 
						} else if (status == "Stopped") {
 | 
				
			||||||
 | 
							this->mPlaybackState = MprisPlaybackState::Stopped;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->mPlaybackState = MprisPlaybackState::Stopped;
 | 
				
			||||||
 | 
							qWarning() << "Received unexpected PlaybackStatus for" << this << status;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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) {
 | 
				
			||||||
 | 
						if (!this->canControl()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set loopState of" << this << "because canControl is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->loopSupported()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set loopState of" << this << "because loop state is not supported.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (loopState == this->mLoopState) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QString loopStatusStr;
 | 
				
			||||||
 | 
						switch (loopState) {
 | 
				
			||||||
 | 
						case MprisLoopState::None: loopStatusStr = "None"; break;
 | 
				
			||||||
 | 
						case MprisLoopState::Track: loopStatusStr = "Track"; break;
 | 
				
			||||||
 | 
						case MprisLoopState::Playlist: loopStatusStr = "Playlist"; break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							qWarning() << "Cannot set loopState of" << this << "to unknown value" << loopState;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->pLoopStatus.set(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->pMinRate.get() || rate > this->pMaxRate.get()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set rate for" << this << "to" << rate
 | 
				
			||||||
 | 
							           << "which is outside of minRate and maxRate" << this->pMinRate.get()
 | 
				
			||||||
 | 
							           << this->pMaxRate.get();
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->pRate.set(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) {
 | 
				
			||||||
 | 
						if (!this->shuffleSupported()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set shuffle for" << this << "because shuffle is not supported.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->canControl()) {
 | 
				
			||||||
 | 
							qWarning() << "Cannot set shuffle state of" << this << "because canControl is false.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->pShuffle.set(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->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();
 | 
				
			||||||
 | 
						if (this->shuffleSupported()) emit this->shuffleSupportedChanged();
 | 
				
			||||||
 | 
						emit this->ready();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::mpris
 | 
				
			||||||
							
								
								
									
										324
									
								
								src/services/mpris/player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								src/services/mpris/player.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,324 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../core/doc.hpp"
 | 
				
			||||||
 | 
					#include "../../dbus/properties.hpp"
 | 
				
			||||||
 | 
					#include "dbus_player.h"
 | 
				
			||||||
 | 
					#include "dbus_player_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::mpris {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MprisPlaybackState: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum {
 | 
				
			||||||
 | 
							Stopped = 0,
 | 
				
			||||||
 | 
							Playing = 1,
 | 
				
			||||||
 | 
							Paused = 2,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(MprisPlaybackState::Enum status);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MprisLoopState: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum {
 | 
				
			||||||
 | 
							None = 0,
 | 
				
			||||||
 | 
							Track = 1,
 | 
				
			||||||
 | 
							Playlist = 2,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(MprisLoopState::Enum status);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! A media player exposed over MPRIS.
 | 
				
			||||||
 | 
					/// A media player exposed over MPRIS.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// > [!WARNING] Support for various functionality and general compliance to
 | 
				
			||||||
 | 
					/// > the MPRIS specification varies wildly by player.
 | 
				
			||||||
 | 
					/// > Always check the associated `canXyz` and `xyzSupported` properties if available.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// > [!INFO] The TrackList and Playlist interfaces were not implemented as we could not
 | 
				
			||||||
 | 
					/// > find any media players using them to test against.
 | 
				
			||||||
 | 
					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 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);
 | 
				
			||||||
 | 
						/// The human readable name of the media player.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString identity READ identity NOTIFY identityChanged);
 | 
				
			||||||
 | 
						/// The current position in the playing track, as seconds, with millisecond precision,
 | 
				
			||||||
 | 
						/// or `0` if `positionSupported` is false.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May only be written to if `canSeek` and `positionSupported` are true.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!WARNING] To avoid excessive property updates wasting CPU while `position` is not
 | 
				
			||||||
 | 
						/// > actively monitored, `position` usually will not update reactively, unless a nonlinear
 | 
				
			||||||
 | 
						/// > change in position occurs, however reading it will always return the current position.
 | 
				
			||||||
 | 
						/// >
 | 
				
			||||||
 | 
						/// > If you want to actively monitor the position, the simplest way it to emit the `positionChanged`
 | 
				
			||||||
 | 
						/// > signal manually for the duration you are monitoring it, Using a [FrameAnimation] if you need
 | 
				
			||||||
 | 
						/// > the value to update smoothly, such as on a slider, or a [Timer] if not, as shown below.
 | 
				
			||||||
 | 
						/// >
 | 
				
			||||||
 | 
						/// > ```qml {filename="Using a FrameAnimation"}
 | 
				
			||||||
 | 
						/// > FrameAnimation {
 | 
				
			||||||
 | 
						/// >   // only emit the signal when the position is actually changing.
 | 
				
			||||||
 | 
						/// >   running: player.playbackState == MprisPlaybackState.Playing
 | 
				
			||||||
 | 
						/// >   // emit the positionChanged signal every frame.
 | 
				
			||||||
 | 
						/// >   onTriggered: player.positionChanged()
 | 
				
			||||||
 | 
						/// > }
 | 
				
			||||||
 | 
						/// > ```
 | 
				
			||||||
 | 
						/// >
 | 
				
			||||||
 | 
						/// > ```qml {filename="Using a Timer"}
 | 
				
			||||||
 | 
						/// > Timer {
 | 
				
			||||||
 | 
						/// >   // only emit the signal when the position is actually changing.
 | 
				
			||||||
 | 
						/// >   running: player.playbackState == MprisPlaybackState.Playing
 | 
				
			||||||
 | 
						/// >   // Make sure the position updates at least once per second.
 | 
				
			||||||
 | 
						/// >   interval: 1000
 | 
				
			||||||
 | 
						/// >   repeat: true
 | 
				
			||||||
 | 
						/// >   // emit the positionChanged signal every second.
 | 
				
			||||||
 | 
						/// >   onTriggered: player.positionChanged()
 | 
				
			||||||
 | 
						/// > }
 | 
				
			||||||
 | 
						/// > ```
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// [FrameAnimation]: https://doc.qt.io/qt-6/qml-qtquick-frameanimation.html
 | 
				
			||||||
 | 
						/// [Timer]: https://doc.qt.io/qt-6/qml-qtqml-timer.html
 | 
				
			||||||
 | 
						Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(bool positionSupported READ positionSupported NOTIFY positionSupportedChanged);
 | 
				
			||||||
 | 
						/// The length of the playing track, as seconds, with millisecond precision,
 | 
				
			||||||
 | 
						/// or the value of `position` if `lengthSupported` is false.
 | 
				
			||||||
 | 
						Q_PROPERTY(qreal length READ length NOTIFY lengthChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(bool lengthSupported READ lengthSupported NOTIFY lengthSupportedChanged);
 | 
				
			||||||
 | 
						/// 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(bool volumeSupported READ volumeSupported NOTIFY volumeSupportedChanged);
 | 
				
			||||||
 | 
						/// Metadata of the current track.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// A map of common properties is available [here](https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata).
 | 
				
			||||||
 | 
						/// Do not count on any of them actually being present.
 | 
				
			||||||
 | 
						Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged);
 | 
				
			||||||
 | 
						/// The playback state of the media player.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// - If `canPlay` is false, you cannot assign the `Playing` state.
 | 
				
			||||||
 | 
						/// - 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(MprisPlaybackState::Enum playbackState READ playbackState WRITE setPlaybackState NOTIFY playbackStateChanged);
 | 
				
			||||||
 | 
						/// 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(MprisLoopState::Enum loopState READ loopState WRITE setLoopState NOTIFY loopStateChanged);
 | 
				
			||||||
 | 
						Q_PROPERTY(bool loopSupported READ loopSupported NOTIFY loopSupportedChanged);
 | 
				
			||||||
 | 
						/// The speed the song is playing at, as a multiplier.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Only values between `minRate` and `maxRate` (inclusive) may be written to the property.
 | 
				
			||||||
 | 
						/// 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);
 | 
				
			||||||
 | 
						/// 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 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);
 | 
				
			||||||
 | 
						/// Uri schemes supported by `openUri`.
 | 
				
			||||||
 | 
						Q_PROPERTY(QList<QString> supportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemesChanged);
 | 
				
			||||||
 | 
						/// Mime types supported by `openUri`.
 | 
				
			||||||
 | 
						Q_PROPERTY(QList<QString> supportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("MprisPlayers can only be acquired from Mpris");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit MprisPlayer(const QString& address, QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Bring the media player to the front of the window stack.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May only be called if `canRaise` is true.
 | 
				
			||||||
 | 
						Q_INVOKABLE void raise();
 | 
				
			||||||
 | 
						/// Quit the media player.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May only be called if `canQuit` is true.
 | 
				
			||||||
 | 
						Q_INVOKABLE void quit();
 | 
				
			||||||
 | 
						/// Open the given URI in the media player.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Many players will silently ignore this, especially if the uri
 | 
				
			||||||
 | 
						/// does not match `supportedUriSchemes` and `supportedMimeTypes`.
 | 
				
			||||||
 | 
						Q_INVOKABLE void openUri(const QString& uri);
 | 
				
			||||||
 | 
						/// Play the next song.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May only be called if `canGoNext` is true.
 | 
				
			||||||
 | 
						Q_INVOKABLE void next();
 | 
				
			||||||
 | 
						/// Play the previous song, or go back to the beginning of the current one.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May only be called if `canGoPrevious` is true.
 | 
				
			||||||
 | 
						Q_INVOKABLE void previous();
 | 
				
			||||||
 | 
						/// Change `position` by an offset.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Even if `positionSupported` is false and you cannot set `position`,
 | 
				
			||||||
 | 
						/// this function may work.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May only be called if `canSeek` is true.
 | 
				
			||||||
 | 
						Q_INVOKABLE void seek(qreal offset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[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 canQuit() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool canRaise() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool canSetFullscreen() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString identity() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qlonglong positionMs() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qreal position() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool positionSupported() const;
 | 
				
			||||||
 | 
						void setPosition(qreal position);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qreal length() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool lengthSupported() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qreal volume() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool volumeSupported() const;
 | 
				
			||||||
 | 
						void setVolume(qreal volume);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QVariantMap metadata() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] MprisPlaybackState::Enum playbackState() const;
 | 
				
			||||||
 | 
						void setPlaybackState(MprisPlaybackState::Enum playbackState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] MprisLoopState::Enum loopState() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool loopSupported() const;
 | 
				
			||||||
 | 
						void setLoopState(MprisLoopState::Enum loopState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] qreal rate() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qreal minRate() const;
 | 
				
			||||||
 | 
						[[nodiscard]] qreal maxRate() const;
 | 
				
			||||||
 | 
						void setRate(qreal rate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool shuffle() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool shuffleSupported() const;
 | 
				
			||||||
 | 
						void setShuffle(bool shuffle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool fullscreen() const;
 | 
				
			||||||
 | 
						void setFullscreen(bool fullscreen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QList<QString> supportedUriSchemes() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QList<QString> supportedMimeTypes() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						QSDOC_HIDE void ready();
 | 
				
			||||||
 | 
						void canControlChanged();
 | 
				
			||||||
 | 
						void canPlayChanged();
 | 
				
			||||||
 | 
						void canPauseChanged();
 | 
				
			||||||
 | 
						void canSeekChanged();
 | 
				
			||||||
 | 
						void canGoNextChanged();
 | 
				
			||||||
 | 
						void canGoPreviousChanged();
 | 
				
			||||||
 | 
						void canQuitChanged();
 | 
				
			||||||
 | 
						void canRaiseChanged();
 | 
				
			||||||
 | 
						void canSetFullscreenChanged();
 | 
				
			||||||
 | 
						void identityChanged();
 | 
				
			||||||
 | 
						void positionChanged();
 | 
				
			||||||
 | 
						void positionSupportedChanged();
 | 
				
			||||||
 | 
						void lengthChanged();
 | 
				
			||||||
 | 
						void lengthSupportedChanged();
 | 
				
			||||||
 | 
						void volumeChanged();
 | 
				
			||||||
 | 
						void volumeSupportedChanged();
 | 
				
			||||||
 | 
						void metadataChanged();
 | 
				
			||||||
 | 
						void playbackStateChanged();
 | 
				
			||||||
 | 
						void loopStateChanged();
 | 
				
			||||||
 | 
						void loopSupportedChanged();
 | 
				
			||||||
 | 
						void rateChanged();
 | 
				
			||||||
 | 
						void minRateChanged();
 | 
				
			||||||
 | 
						void maxRateChanged();
 | 
				
			||||||
 | 
						void shuffleChanged();
 | 
				
			||||||
 | 
						void shuffleSupportedChanged();
 | 
				
			||||||
 | 
						void fullscreenChanged();
 | 
				
			||||||
 | 
						void supportedUriSchemesChanged();
 | 
				
			||||||
 | 
						void supportedMimeTypesChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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<QString> pIdentity {this->appProperties, "Identity"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanQuit {this->appProperties, "CanQuit"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanRaise {this->appProperties, "CanRaise"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pFullscreen {this->appProperties, "Fullscreen", false, false};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanSetFullscreen {this->appProperties, "CanSetFullscreen", false, false};
 | 
				
			||||||
 | 
						dbus::DBusProperty<QList<QString>> pSupportedUriSchemes {this->appProperties, "SupportedUriSchemes"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<QList<QString>> pSupportedMimeTypes {this->appProperties, "SupportedMimeTypes"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbus::DBusPropertyGroup playerProperties;
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanControl {this->playerProperties, "CanControl"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanPlay {this->playerProperties, "CanPlay"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanPause {this->playerProperties, "CanPause"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanSeek {this->playerProperties, "CanSeek"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanGoNext {this->playerProperties, "CanGoNext"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pCanGoPrevious {this->playerProperties, "CanGoPrevious"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<qlonglong> pPosition {this->playerProperties, "Position", 0, false}; // "required"
 | 
				
			||||||
 | 
						dbus::DBusProperty<double> pVolume {this->playerProperties, "Volume", 1, false}; // "required"
 | 
				
			||||||
 | 
						dbus::DBusProperty<QVariantMap> pMetadata {this->playerProperties, "Metadata"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<QString> pPlaybackStatus {this->playerProperties, "PlaybackStatus"};
 | 
				
			||||||
 | 
						dbus::DBusProperty<QString> pLoopStatus {this->playerProperties, "LoopStatus", "", false};
 | 
				
			||||||
 | 
						dbus::DBusProperty<double> pRate {this->playerProperties, "Rate", 1, false}; // "required"
 | 
				
			||||||
 | 
						dbus::DBusProperty<double> pMinRate {this->playerProperties, "MinimumRate", 1, false}; // "required"
 | 
				
			||||||
 | 
						dbus::DBusProperty<double> pMaxRate {this->playerProperties, "MaximumRate", 1, false}; // "required"
 | 
				
			||||||
 | 
						dbus::DBusProperty<bool> pShuffle {this->playerProperties, "Shuffle", false, 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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::mpris
 | 
				
			||||||
							
								
								
									
										126
									
								
								src/services/mpris/watcher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/services/mpris/watcher.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,126 @@
 | 
				
			||||||
 | 
					#include "watcher.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbusconnectioninterface.h>
 | 
				
			||||||
 | 
					#include <qdbusservicewatcher.h>
 | 
				
			||||||
 | 
					#include <qlist.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmllist.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "player.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::mpris {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logMprisWatcher, "quickshell.service.mpris.watcher", QtWarningMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MprisWatcher::MprisWatcher(QObject* parent): QObject(parent) {
 | 
				
			||||||
 | 
						qCDebug(logMprisWatcher) << "Starting MprisWatcher";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto bus = QDBusConnection::sessionBus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!bus.isConnected()) {
 | 
				
			||||||
 | 
							qCWarning(logMprisWatcher) << "Could not connect to 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);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->serviceWatcher.setWatchMode(
 | 
				
			||||||
 | 
						    QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->serviceWatcher.addWatchedService("org.mpris.MediaPlayer2*");
 | 
				
			||||||
 | 
						this->serviceWatcher.setConnection(bus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->registerExisting();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisWatcher::registerExisting() {
 | 
				
			||||||
 | 
						const QStringList& list = QDBusConnection::sessionBus().interface()->registeredServiceNames();
 | 
				
			||||||
 | 
						for (const QString& service: list) {
 | 
				
			||||||
 | 
							if (service.startsWith("org.mpris.MediaPlayer2")) {
 | 
				
			||||||
 | 
								qCDebug(logMprisWatcher).noquote() << "Found Mpris service" << service;
 | 
				
			||||||
 | 
								this->registerPlayer(service);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisWatcher::onServiceRegistered(const QString& service) {
 | 
				
			||||||
 | 
						if (service.startsWith("org.mpris.MediaPlayer2")) {
 | 
				
			||||||
 | 
							qCDebug(logMprisWatcher).noquote() << "Mpris service " << service << " registered.";
 | 
				
			||||||
 | 
							this->registerPlayer(service);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							qCWarning(logMprisWatcher) << "Got a registration event for untracked service" << service;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisWatcher::onServiceUnregistered(const QString& service) {
 | 
				
			||||||
 | 
						if (auto* player = this->mPlayers.value(service)) {
 | 
				
			||||||
 | 
							player->deleteLater();
 | 
				
			||||||
 | 
							this->mPlayers.remove(service);
 | 
				
			||||||
 | 
							qCDebug(logMprisWatcher) << "Unregistered MprisPlayer" << service;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							qCWarning(logMprisWatcher) << "Got service unregister event for untracked service" << service;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisWatcher::onPlayerReady() {
 | 
				
			||||||
 | 
						auto* player = qobject_cast<MprisPlayer*>(this->sender());
 | 
				
			||||||
 | 
						this->readyPlayers.push_back(player);
 | 
				
			||||||
 | 
						emit this->playersChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisWatcher::onPlayerDestroyed(QObject* object) {
 | 
				
			||||||
 | 
						auto* player = static_cast<MprisPlayer*>(object); // NOLINT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->readyPlayers.removeOne(player)) {
 | 
				
			||||||
 | 
							emit this->playersChanged();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QQmlListProperty<MprisPlayer> MprisWatcher::players() {
 | 
				
			||||||
 | 
						return QQmlListProperty<MprisPlayer>(
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    nullptr,
 | 
				
			||||||
 | 
						    &MprisWatcher::playersCount,
 | 
				
			||||||
 | 
						    &MprisWatcher::playerAt
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qsizetype MprisWatcher::playersCount(QQmlListProperty<MprisPlayer>* property) {
 | 
				
			||||||
 | 
						return static_cast<MprisWatcher*>(property->object)->readyPlayers.count(); // NOLINT
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MprisPlayer* MprisWatcher::playerAt(QQmlListProperty<MprisPlayer>* property, qsizetype index) {
 | 
				
			||||||
 | 
						return static_cast<MprisWatcher*>(property->object)->readyPlayers.at(index); // NOLINT
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MprisWatcher::registerPlayer(const QString& address) {
 | 
				
			||||||
 | 
						if (this->mPlayers.contains(address)) {
 | 
				
			||||||
 | 
							qCDebug(logMprisWatcher) << "Skipping duplicate registration of MprisPlayer" << address;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto* player = new MprisPlayer(address, this);
 | 
				
			||||||
 | 
						if (!player->isValid()) {
 | 
				
			||||||
 | 
							qCWarning(logMprisWatcher) << "Ignoring invalid MprisPlayer registration of" << address;
 | 
				
			||||||
 | 
							delete player;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mPlayers.insert(address, player);
 | 
				
			||||||
 | 
						QObject::connect(player, &MprisPlayer::ready, this, &MprisWatcher::onPlayerReady);
 | 
				
			||||||
 | 
						QObject::connect(player, &QObject::destroyed, this, &MprisWatcher::onPlayerDestroyed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logMprisWatcher) << "Registered MprisPlayer" << address;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::mpris
 | 
				
			||||||
							
								
								
									
										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 <qhash.h>
 | 
				
			||||||
 | 
					#include <qlist.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qqmllist.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "player.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::service::mpris {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Provides access to MprisPlayers.
 | 
				
			||||||
 | 
					class MprisWatcher: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_NAMED_ELEMENT(Mpris);
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
						/// All connected MPRIS players.
 | 
				
			||||||
 | 
						Q_PROPERTY(QQmlListProperty<MprisPlayer> players READ players NOTIFY playersChanged);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit MprisWatcher(QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QQmlListProperty<MprisPlayer> players();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void playersChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onServiceRegistered(const QString& service);
 | 
				
			||||||
 | 
						void onServiceUnregistered(const QString& service);
 | 
				
			||||||
 | 
						void onPlayerReady();
 | 
				
			||||||
 | 
						void onPlayerDestroyed(QObject* object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						static qsizetype playersCount(QQmlListProperty<MprisPlayer>* property);
 | 
				
			||||||
 | 
						static MprisPlayer* playerAt(QQmlListProperty<MprisPlayer>* property, qsizetype index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void registerExisting();
 | 
				
			||||||
 | 
						void registerPlayer(const QString& address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QDBusServiceWatcher serviceWatcher;
 | 
				
			||||||
 | 
						QHash<QString, MprisPlayer*> mPlayers;
 | 
				
			||||||
 | 
						QList<MprisPlayer*> readyPlayers;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::service::mpris
 | 
				
			||||||
| 
						 | 
					@ -54,14 +54,14 @@ public:
 | 
				
			||||||
	dbus::DBusProperty<QString> status {this->properties, "Status"};
 | 
						dbus::DBusProperty<QString> status {this->properties, "Status"};
 | 
				
			||||||
	dbus::DBusProperty<QString> category {this->properties, "Category"};
 | 
						dbus::DBusProperty<QString> category {this->properties, "Category"};
 | 
				
			||||||
	dbus::DBusProperty<quint32> windowId {this->properties, "WindowId"};
 | 
						dbus::DBusProperty<quint32> windowId {this->properties, "WindowId"};
 | 
				
			||||||
	dbus::DBusProperty<QString> iconThemePath {this->properties, "IconThemePath"};
 | 
						dbus::DBusProperty<QString> iconThemePath {this->properties, "IconThemePath", "", false};
 | 
				
			||||||
	dbus::DBusProperty<QString> iconName {this->properties, "IconName"};
 | 
						dbus::DBusProperty<QString> iconName {this->properties, "IconName", "", false}; // IconPixmap may be set
 | 
				
			||||||
	dbus::DBusProperty<DBusSniIconPixmapList> iconPixmaps {this->properties, "IconPixmap"};
 | 
						dbus::DBusProperty<DBusSniIconPixmapList> iconPixmaps {this->properties, "IconPixmap", {}, false}; // IconName may be set
 | 
				
			||||||
	dbus::DBusProperty<QString> overlayIconName {this->properties, "OverlayIconName"};
 | 
						dbus::DBusProperty<QString> overlayIconName {this->properties, "OverlayIconName"};
 | 
				
			||||||
	dbus::DBusProperty<DBusSniIconPixmapList> overlayIconPixmaps {this->properties, "OverlayIconPixmap"};
 | 
						dbus::DBusProperty<DBusSniIconPixmapList> overlayIconPixmaps {this->properties, "OverlayIconPixmap"};
 | 
				
			||||||
	dbus::DBusProperty<QString> attentionIconName {this->properties, "AttentionIconName"};
 | 
						dbus::DBusProperty<QString> attentionIconName {this->properties, "AttentionIconName"};
 | 
				
			||||||
	dbus::DBusProperty<DBusSniIconPixmapList> attentionIconPixmaps {this->properties, "AttentionIconPixmap"};
 | 
						dbus::DBusProperty<DBusSniIconPixmapList> attentionIconPixmaps {this->properties, "AttentionIconPixmap"};
 | 
				
			||||||
	dbus::DBusProperty<QString> attentionMovieName {this->properties, "AttentionMovieName"};
 | 
						dbus::DBusProperty<QString> attentionMovieName {this->properties, "AttentionMovieName", "", false};
 | 
				
			||||||
	dbus::DBusProperty<DBusSniTooltip> tooltip {this->properties, "ToolTip"};
 | 
						dbus::DBusProperty<DBusSniTooltip> tooltip {this->properties, "ToolTip"};
 | 
				
			||||||
	dbus::DBusProperty<bool> isMenu {this->properties, "ItemIsMenu"};
 | 
						dbus::DBusProperty<bool> isMenu {this->properties, "ItemIsMenu"};
 | 
				
			||||||
	dbus::DBusProperty<QDBusObjectPath> menuPath {this->properties, "Menu"};
 | 
						dbus::DBusProperty<QDBusObjectPath> menuPath {this->properties, "Menu"};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue