diff --git a/frame/CMakeLists.txt b/frame/CMakeLists.txt index b54909545..6325d73b9 100644 --- a/frame/CMakeLists.txt +++ b/frame/CMakeLists.txt @@ -18,6 +18,7 @@ set(PUBLIC_HEADERS wayland/xdgactivation.h models/listtotableproxymodel.h dsutility.h + foldercontent.h ) set(PRIVATE_HEADERS @@ -77,6 +78,7 @@ add_library(dde-shell-frame SHARED popupwindow.cpp ddeshell_qml.qrc dsutility.cpp + foldercontent.cpp ) set_target_properties(dde-shell-frame PROPERTIES diff --git a/frame/foldercontent.cpp b/frame/foldercontent.cpp new file mode 100644 index 000000000..a72ebb956 --- /dev/null +++ b/frame/foldercontent.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +#include "foldercontent.h" + +#include +#include +#include +#include +#include +#include + +DS_BEGIN_NAMESPACE + +FolderContentRegistry *FolderContentRegistry::instance() +{ + static auto registry = new FolderContentRegistry(qApp); + return registry; +} + +QString FolderContentRegistry::reference(const QString &source, const QString &id) +{ + return QStringLiteral("folder/%1/%2").arg(source, QString::fromLatin1(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); +} + +bool FolderContentRegistry::decode(const QString &value, QString *source, QString *id) +{ + const auto parts = value.split('/'); + static const QRegularExpression sourcePattern(QStringLiteral("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")); + if (parts.size() != 3 || parts[0] != "folder" || !sourcePattern.match(parts[1]).hasMatch()) + return false; + const auto bytes = QByteArray::fromBase64(parts[2].toLatin1(), + QByteArray::Base64UrlEncoding | QByteArray::AbortOnBase64DecodingErrors); + *source = parts[1]; + *id = QString::fromUtf8(bytes); + return !id->isEmpty() && reference(*source, *id) == value; +} + +bool FolderContentRegistry::isReference(const QString &value) +{ + QString source, id; + return decode(value, &source, &id); +} + +QString FolderContentRegistry::referenceWithId(const QString &value, const QString &id) const +{ + QString source, current; + if (id.isEmpty() || !decode(value, &source, ¤t)) return {}; + return reference(source, id); +} + +FolderContentProvider *FolderContentRegistry::provider(const QString &source) +{ + if (m_providers.contains(source)) + return m_providers.value(source); + + // Content providers live in the regular shell plugin directory and follow + // the same file naming as every other shell plugin: + // org.deepin.ds.folderprovider..so + const auto fileName = QStringLiteral("org.deepin.ds.folderprovider.%1.so").arg(source); + QStringList dirs { + QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("../plugins"), + QStringLiteral(DDE_SHELL_PLUGIN_INSTALL_DIR) + }; + const auto runtimePaths = qEnvironmentVariable("DDE_SHELL_PLUGIN_PATH"); + if (!runtimePaths.isEmpty()) + dirs.append(runtimePaths.split(QDir::listSeparator(), Qt::SkipEmptyParts)); + dirs.append(QCoreApplication::libraryPaths()); + + for (const auto &dir : dirs) { + const auto path = QDir(dir).filePath(fileName); + if (!QFileInfo::exists(path)) + continue; + auto loader = new QPluginLoader(path, this); + auto object = loader->instance(); + auto contentProvider = qobject_cast(object); + if (contentProvider) { + m_providers.insert(source, contentProvider); + return contentProvider; + } + qWarning() << "Failed to load folder content provider:" << path + << (object ? QStringLiteral("FolderContentProvider interface mismatch") : loader->errorString()); + delete loader; + } + qWarning() << "Folder content provider unavailable:" << fileName << "search paths:" << dirs; + return nullptr; +} + +QString FolderContentRegistry::normalizeReference(const QString &value) +{ + QString source, id; + if (!decode(value, &source, &id)) return {}; + auto factory = provider(source); + const auto normalized = factory ? factory->normalizeId(id) : QString(); + return normalized.isEmpty() ? QString() : reference(source, normalized); +} + +FolderPresentation *FolderContentRegistry::create(const QString &value, QObject *parent) +{ + QString source, id; + if (!decode(value, &source, &id)) return nullptr; + if (auto factory = provider(source)) + return factory->create(id, parent); + // Keep pins when a provider is temporarily unavailable. + return new FolderPresentation(parent); +} + +DS_END_NAMESPACE diff --git a/frame/foldercontent.h b/frame/foldercontent.h new file mode 100644 index 000000000..91f87a9ed --- /dev/null +++ b/frame/foldercontent.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include "dsglobal.h" +#include +#include +#include +#include + +DS_BEGIN_NAMESPACE + +// Metadata for the pinned icon only. The provider's QML view owns all content +// and operations; the framework and task manager do not model its entries. +class DS_SHARE FolderPresentation : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString title READ title NOTIFY presentationChanged) + Q_PROPERTY(QVariantList previewIcons READ previewIcons NOTIFY presentationChanged) + Q_PROPERTY(QUrl iconUrl READ iconUrl CONSTANT) + Q_PROPERTY(QUrl contentUrl READ contentUrl CONSTANT) +public: + using QObject::QObject; + virtual QString title() const { return tr("Folder"); } + virtual QVariantList previewIcons() const { return {}; } + virtual QUrl iconUrl() const { return {}; } + virtual QUrl contentUrl() const { return {}; } +signals: + void presentationChanged(); + void identityChanged(const QString &id); + void contentLost(); +}; + +class FolderContentProvider +{ +public: + virtual ~FolderContentProvider() = default; + virtual QString normalizeId(const QString &id) const = 0; + virtual FolderPresentation *create(const QString &id, QObject *parent) = 0; +}; + +class DS_SHARE FolderContentRegistry : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; + static FolderContentRegistry *instance(); + static bool isReference(const QString &reference); + static QString reference(const QString &source, const QString &id); + // Rebuild an existing reference with a new provider id, keeping its source. + QString referenceWithId(const QString &reference, const QString &id) const; + QString normalizeReference(const QString &reference); + FolderPresentation *create(const QString &reference, QObject *parent); +private: + FolderContentProvider *provider(const QString &source); + static bool decode(const QString &reference, QString *source, QString *id); + QHash m_providers; +}; + +DS_END_NAMESPACE + +#define DS_FOLDER_CONTENT_PROVIDER_IID "org.deepin.ds.FolderContentProvider/1.0" +Q_DECLARE_INTERFACE(DS_NAMESPACE::FolderContentProvider, DS_FOLDER_CONTENT_PROVIDER_IID)