Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion interfaces/constants.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2011 - 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2011 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

Expand Down Expand Up @@ -114,6 +114,7 @@ enum PluginFlag {
Attribute_CanInsert = 0x400, // 插件属性-是否支持在其前面插入其他的插件,普通的快捷插件是支持的
Attribute_CanSetting = 0x800, // 插件属性-是否可以在控制中心设置显示或隐藏,如果设置了这个属性,请实现PluginsItemInterfaceV2::icon 接口,并返回在`控制中心-个性化-任务栏-插件区域`中显示的图标
Attribute_ForceDock = 0x1000, // 插件属性-强制显示在任务栏上
Attribute_HasCard = 0x2000, // 插件属性-是否提供卡片区域 surface

Attribute_Normal = Attribute_CanDrag | Attribute_CanInsert | Attribute_CanSetting, // 普通插件

Expand Down
41 changes: 41 additions & 0 deletions interfaces/pluginsiteminterface_v3.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#ifndef PLUGINSITEMINTERFACE_V3_H
#define PLUGINSITEMINTERFACE_V3_H

#include "pluginsiteminterface_v2.h"

#include <QString>
#include <QWindow>

class PluginsItemInterfaceV3 : public PluginsItemInterfaceV2
{
public:
/**
* @brief Item key that should be exported as a card surface.
*
* The item key is still scoped by pluginName(), so the compositor can use
* "pluginName::itemKey" as a stable surface id.
*/
virtual QString cardItemKey() const { return {}; }

/**
* @brief Native window for the card item.
*
* The loader exposes this window to the dock compositor as a Wayland
* surface. QML or QWidget based UI should be handled by plugin itself.
* The plugin keeps ownership of the returned window.
*/
virtual QWindow *cardWindow() const { return nullptr; }
};

QT_BEGIN_NAMESPACE

#define ModuleInterface_iid_V3 "com.deepin.dock.PluginsItemInterface_V3"

Q_DECLARE_INTERFACE(PluginsItemInterfaceV3, ModuleInterface_iid_V3)
QT_END_NAMESPACE

#endif // PLUGINSITEMINTERFACE_V3_H
4 changes: 3 additions & 1 deletion plugins/dde-dock/media/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ file(GLOB_RECURSE SRCS
)

find_package(PkgConfig REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets Svg DBus)
find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets Svg DBus Quick Qml)
find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Widget)

add_definitions("${QT_DEFINITIONS} -DQT_PLUGIN")
Expand All @@ -33,6 +33,8 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::DBus
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Svg
Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::Qml
dockpluginmanager-interface
)

Expand Down
209 changes: 196 additions & 13 deletions plugins/dde-dock/media/mediacontroller.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

Expand All @@ -7,9 +7,43 @@
#include "mediamodel.h"
#include <qdbusinterface.h>

#include <QDBusPendingReply>

Check warning on line 10 in plugins/dde-dock/media/mediacontroller.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusPendingReply> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusPendingCallWatcher>

Check warning on line 11 in plugins/dde-dock/media/mediacontroller.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusPendingCallWatcher> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDebug>

Check warning on line 12 in plugins/dde-dock/media/mediacontroller.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDebug> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QImageReader>

Check warning on line 13 in plugins/dde-dock/media/mediacontroller.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QImageReader> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QUrl>

Check warning on line 14 in plugins/dde-dock/media/mediacontroller.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QUrl> not found. Please note: Cppcheck does not need standard library headers to get proper results.

namespace {

QString metadataText(const QVariant &value)
{
if (value.canConvert<QStringList>()) {
return value.toStringList().join(QStringLiteral(", "));
}

if (value.metaType().id() == QMetaType::QVariantList) {
QStringList parts;
const auto values = value.toList();
for (const auto &item : values) {
const QString text = item.toString();
if (!text.isEmpty()) {
parts.append(text);
}
}
return parts.join(QStringLiteral(", "));
}

return value.toString();
}

QString playerIdentity(const QString &service)
{
QString fallback = service;
fallback.remove(QStringLiteral("org.mpris.MediaPlayer2."));
return fallback;
}

}

MediaController::MediaController()
: m_mediaPlayer2(nullptr)
Expand All @@ -22,17 +56,34 @@

void MediaController::loadMediaPath(const QString &path)
{
DBusMediaPlayer2 *newMediaPlayer = new DBusMediaPlayer2(path, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus(), this);
QDBusInterface propertiesInter(path,
QStringLiteral("/org/mpris/MediaPlayer2"),
QStringLiteral("org.freedesktop.DBus.Properties"),
QDBusConnection::sessionBus());
const QDBusPendingCall asyncCall = propertiesInter.asyncCall(QStringLiteral("Get"),
QStringLiteral("org.mpris.MediaPlayer2"),
QStringLiteral("CanShowInUI"));
auto *watcher = new QDBusPendingCallWatcher(asyncCall, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this,
[this, path](QDBusPendingCallWatcher *watcher) {
const QDBusPendingReply<QDBusVariant> reply = *watcher;
watcher->deleteLater();

if (!newMediaPlayer->canShowInUI()) {
newMediaPlayer->deleteLater();
return;
}
const bool showInUI = reply.isError() ? true : reply.value().variant().toBool();
if (showInUI) {
finishLoadMediaPath(path);
}
});
}

if (!m_mediaPlayer2)
Q_EMIT mediaAcquired();
void MediaController::finishLoadMediaPath(const QString &path)
{
DBusMediaPlayer2 *newMediaPlayer = new DBusMediaPlayer2(path, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus(), this);

const bool wasEmpty = !m_mediaPlayer2;
m_path = path;
MediaModel::ref().setPath(path);
m_appName = playerIdentity(path);

// save paths
if (!m_mediaPaths.contains(path))
Expand All @@ -46,9 +97,24 @@
connect(m_mediaPlayer2, &DBusMediaPlayer2::MetadataChanged, this, &MediaController::onMetaDataChanged);
connect(m_mediaPlayer2, &DBusMediaPlayer2::PlaybackStatusChanged, this, &MediaController::onPlaybackStatusChanged);
connect(m_mediaPlayer2, &DBusMediaPlayer2::CanControlChanged, &MediaModel::ref(), &MediaModel::onCanControlChanged);
connect(m_mediaPlayer2, &DBusMediaPlayer2::CanGoNextChanged, this, &MediaController::canGoNextChanged);
connect(m_mediaPlayer2, &DBusMediaPlayer2::CanGoPreviousChanged, this, &MediaController::canGoPreviousChanged);
connect(m_mediaPlayer2, &DBusMediaPlayer2::CanControlChanged, this, [this] {
Q_EMIT canTogglePlaybackChanged(canTogglePlayback());
});
connect(m_mediaPlayer2, &DBusMediaPlayer2::CanPlayChanged, this, [this] {
Q_EMIT canTogglePlaybackChanged(canTogglePlayback());
});
connect(m_mediaPlayer2, &DBusMediaPlayer2::CanPauseChanged, this, [this] {
Q_EMIT canTogglePlaybackChanged(canTogglePlayback());
});

syncPlayerProperties();
onMetaDataChanged();
onPlaybackStatusChanged();

if (wasEmpty)
Q_EMIT mediaAcquired();
}

void MediaController::onMetaDataChanged()
Expand All @@ -57,18 +123,28 @@

const auto &meta = m_mediaPlayer2->metadata();
MediaModel::MediaInfo info;
info.title = meta.value("xesam:title").toString();
info.artist = meta.value("xesam:artist").toString();
info.pixmap = QPixmap(QUrl(meta.value("mpris:artUrl").toString()).toLocalFile()).scaled(QSize(32, 32), Qt::IgnoreAspectRatio);
info.title = metadataText(meta.value("xesam:title"));
info.artist = metadataText(meta.value("xesam:artist"));
const QString artSource = meta.value("mpris:artUrl").toString();

const QString artPath = QUrl(artSource).toLocalFile();
if (!artPath.isEmpty()) {
QImageReader reader(artPath);
reader.setScaledSize(QSize(32, 32));
info.pixmap = QPixmap::fromImage(reader.read());
}
MediaModel::ref().setMediaInfo(info);
Q_EMIT mediaInfoChanged();
}

void MediaController::onPlaybackStatusChanged()
{
if (!m_mediaPlayer2)
return;
const QString &stat = m_mediaPlayer2->playbackStatus();
MediaModel::ref().setPlayState(stat == "Playing");
const bool playing = stat == "Playing";
MediaModel::ref().setPlayState(playing);
Q_EMIT playingChanged(playing);
}

void MediaController::removeMediaPath(const QString &path)
Expand All @@ -85,6 +161,12 @@

m_mediaPlayer2->deleteLater();
m_mediaPlayer2 = nullptr;
m_path.clear();
MediaModel::ref().setPath(QString());
MediaModel::ref().setMediaInfo(MediaModel::MediaInfo());
MediaModel::ref().setPlayState(false);
MediaModel::ref().onCanControlChanged(false);
clearCardState();

Q_EMIT mediaLosted();
}
Expand All @@ -96,6 +178,20 @@
}
}

void MediaController::previous()
{
if (m_mediaPlayer2) {
m_mediaPlayer2->Previous();
}
}

void MediaController::togglePlayback()
{
if (m_mediaPlayer2) {
m_mediaPlayer2->PlayPause();
}
}

void MediaController::raise()
{
if (!m_path.isEmpty()) {
Expand All @@ -121,4 +217,91 @@
bool MediaController::isWorking() const
{
return m_mediaPlayer2 != nullptr;
}
}

void MediaController::syncPlayerProperties()
{
if (m_mediaPlayer2) {
MediaModel::ref().onCanControlChanged(m_mediaPlayer2->canControl());
}

Q_EMIT canGoNextChanged(canGoNext());
Q_EMIT canGoPreviousChanged(canGoPrevious());
Q_EMIT canTogglePlaybackChanged(canTogglePlayback());
}

bool MediaController::playing() const
{
return m_mediaPlayer2 && m_mediaPlayer2->playbackStatus() == QStringLiteral("Playing");
}

bool MediaController::canGoNext() const
{
return m_mediaPlayer2 && m_mediaPlayer2->canGoNext();
}

bool MediaController::canGoPrevious() const
{
return m_mediaPlayer2 && m_mediaPlayer2->canGoPrevious();
}

bool MediaController::canTogglePlayback() const
{
return m_mediaPlayer2
&& m_mediaPlayer2->canControl()
&& (m_mediaPlayer2->canPlay() || m_mediaPlayer2->canPause());
}

QString MediaController::titleText() const
{
if (!m_mediaPlayer2) {
return tr("Music");
}

const auto metadata = m_mediaPlayer2->metadata();
const QString title = metadataText(metadata.value("xesam:title"));
if (!title.isEmpty()) {
return title;
}

const QString artist = metadataText(metadata.value("xesam:artist"));
if (!artist.isEmpty()) {
return artist;
}

return tr("Music");
}

QString MediaController::subtitleText() const
{
if (m_mediaPlayer2) {
const auto metadata = m_mediaPlayer2->metadata();
const QString title = metadataText(metadata.value("xesam:title"));
const QString artist = metadataText(metadata.value("xesam:artist"));
if (!artist.isEmpty() && artist != title) {
return artist;
}
}

return m_appName;
}

QString MediaController::artSource() const
{
return m_mediaPlayer2
? m_mediaPlayer2->metadata().value("mpris:artUrl").toString()
: QString();
}

bool MediaController::hasArt() const
{
return !artSource().isEmpty();
}

void MediaController::clearCardState()
{
m_appName.clear();
Q_EMIT mediaInfoChanged();
Q_EMIT playingChanged(false);
syncPlayerProperties();
}
Loading
Loading