Skip to content
Draft
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 debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ Package: treeland-examples
Section: dde
Architecture: any
Multi-Arch: same
Depends: ${misc:Depends},
Depends: qt6-shader-baker,
Comment thread
zzxyb marked this conversation as resolved.
${misc:Depends},
${shlibs:Depends},
Description: examples for treeland.

Expand Down
238 changes: 237 additions & 1 deletion examples/test_set_wallpaper/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@
#include <QGuiApplication>
#include <QScreen>
#include <QDebug>
#include <QSet>

Check warning on line 12 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 13 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 14 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 15 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 16 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 17 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 18 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 19 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 20 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 21 in examples/test_set_wallpaper/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QProcess> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QSaveFile>
#include <QStandardPaths>
#include <qpa/qplatformnativeinterface.h>

#include <unistd.h>

static QScreen *findScreenByName(const QString &name)
{
if (name.isEmpty())
Expand Down Expand Up @@ -42,6 +53,210 @@
return mime.isValid() && mime.name().startsWith("video/");
}

static bool isFragmentShaderFile(const QString &path)
{
return QFileInfo(path).suffix().compare(QStringLiteral("frag"), Qt::CaseInsensitive) == 0;
}

static QByteArray shaderSourceWithoutVersion(const QByteArray &source)
{
QByteArray result;
const QList<QByteArray> lines = source.split('\n');
for (const QByteArray &line : lines) {
if (line.trimmed().startsWith("#version"))
continue;

result += line;
result += '\n';
}
return result;
}

static QByteArray shaderBakeSource(const QByteArray &source)
{
if (!source.contains("mainImage"))
return source;

QByteArray baked;
baked += "#version 440\n";
baked += "layout(location = 0) in vec2 qt_TexCoord0;\n";
baked += "layout(location = 0) out vec4 fragColor;\n";
baked += "layout(std140, binding = 0) uniform buf {\n";
baked += " mat4 qt_Matrix;\n";
baked += " float qt_Opacity;\n";
baked += " float iTime;\n";
baked += " vec2 iResolution;\n";
baked += " vec4 iMouse;\n";
baked += "};\n";
baked += shaderSourceWithoutVersion(source);
baked += "\nvoid main() {\n";
baked += " vec2 fragCoord = vec2(qt_TexCoord0.x, 1.0 - qt_TexCoord0.y) * iResolution;\n";
baked += " mainImage(fragColor, fragCoord);\n";
baked += " fragColor *= qt_Opacity;\n";
baked += "}\n";
return baked;
}

static bool writeFileAtomically(const QString &path, const QByteArray &data)
{
QSaveFile file(path);
if (!file.open(QIODevice::WriteOnly))
return false;

if (file.write(data) != data.size())
return false;

if (!file.commit())
return false;

QFile::setPermissions(path,
QFileDevice::ReadOwner | QFileDevice::WriteOwner
| QFileDevice::ReadGroup
| QFileDevice::ReadOther);
return true;
}

static bool ensureSharedDirectory(const QString &path)
{
QDir dir;
if (!dir.mkpath(path))
return false;

return QFile::setPermissions(path,
QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner
| QFileDevice::ReadGroup | QFileDevice::ExeGroup
| QFileDevice::ReadOther | QFileDevice::ExeOther);
}

static bool hasCurrentShaderPackageVersion(const QString &metadataPath)
{
QFile file(metadataPath);
if (!file.open(QIODevice::ReadOnly))
return false;

const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
return doc.isObject() && doc.object()[QStringLiteral("packageVersion")].toInt() == 3;
}

static QString findQsbExecutable()
{
const QString qsb = QStandardPaths::findExecutable(QStringLiteral("qsb"));
if (!qsb.isEmpty())
return qsb;

const QString qt6Qsb = QStringLiteral("/usr/lib/qt6/bin/qsb");
if (QFileInfo(qt6Qsb).isExecutable())
return qt6Qsb;

return QString();
}

static QString shaderPackageRoot()
{
return QDir::temp().filePath(QStringLiteral("treeland-wallpaper-shader/%1").arg(getuid()));
}

static bool ensureShaderPackage(const QString &fragmentPath, QString *packagePath)
{
QFile fragment(fragmentPath);
if (!fragment.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open fragment shader:" << fragmentPath << fragment.errorString();
return false;
}

const QByteArray source = fragment.readAll();
const QByteArray hash = QCryptographicHash::hash(source, QCryptographicHash::Sha256).toHex();
QDir root(shaderPackageRoot());
if (!ensureSharedDirectory(root.absolutePath())
|| !ensureSharedDirectory(root.filePath(QString::fromLatin1(hash)))) {
qCritical() << "Failed to create shader wallpaper cache directory:" << root.absolutePath();
return false;
}

QDir package(root.filePath(QString::fromLatin1(hash)));
const QString sourcePath = package.filePath(QStringLiteral("fragment.frag"));
const QString bakePath = package.filePath(QStringLiteral("fragment.qt.frag"));
const QString qsbPath = package.filePath(QStringLiteral("fragment.qsb"));
const QString qsbTempPath = package.filePath(QStringLiteral("fragment.qsb.tmp"));
const QString metadataPath = package.filePath(QStringLiteral("metadata.json"));

if (!QFileInfo::exists(qsbPath) || !hasCurrentShaderPackageVersion(metadataPath)) {
if (!writeFileAtomically(sourcePath, source)) {
qCritical() << "Failed to write shader source:" << sourcePath;
return false;
}

if (!writeFileAtomically(bakePath, shaderBakeSource(source))) {
qCritical() << "Failed to write shader bake source:" << bakePath;
return false;
}

const QString qsb = findQsbExecutable();
if (qsb.isEmpty()) {
qCritical() << "Cannot find qsb. Install qt6-shader-baker to import .frag wallpaper.";
return false;
}

QFile::remove(qsbTempPath);

QProcess process;
process.setProgram(qsb);
process.setArguments({
QStringLiteral("--glsl"), QStringLiteral("100es,120,150"),
QStringLiteral("--hlsl"), QStringLiteral("50"),
QStringLiteral("--msl"), QStringLiteral("12"),
QStringLiteral("-o"), qsbTempPath,
bakePath,
});
process.start();
if (!process.waitForFinished(30000)) {
process.kill();
process.waitForFinished();
qCritical() << "Timed out compiling fragment shader with qsb:" << bakePath;
QFile::remove(qsbTempPath);
return false;
}

if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
qCritical().noquote() << "Failed to compile fragment shader with qsb:"
<< QString::fromLocal8Bit(process.readAllStandardError());
QFile::remove(qsbTempPath);
return false;
}

if (!QFileInfo(qsbTempPath).isFile() || QFileInfo(qsbTempPath).size() <= 0) {
qCritical() << "qsb did not produce a valid shader package output:" << qsbTempPath;
QFile::remove(qsbTempPath);
return false;
}

QFile::remove(qsbPath);
if (!QFile::rename(qsbTempPath, qsbPath)) {
qCritical() << "Failed to install compiled shader package output:" << qsbPath;
QFile::remove(qsbTempPath);
return false;
}

QJsonObject metadata;
metadata[QStringLiteral("packageVersion")] = 3;
metadata[QStringLiteral("type")] = QStringLiteral("shader");
metadata[QStringLiteral("fragmentShader")] = QStringLiteral("fragment.qsb");
metadata[QStringLiteral("sourceShader")] = QStringLiteral("fragment.frag");
metadata[QStringLiteral("uniforms")] = QJsonObject {
{ QStringLiteral("iTime"), true },
{ QStringLiteral("iResolution"), true },
{ QStringLiteral("iMouse"), false },
};
if (!writeFileAtomically(metadataPath, QJsonDocument(metadata).toJson(QJsonDocument::Indented))) {
qCritical() << "Failed to write shader wallpaper metadata:" << metadataPath;
return false;
}
}

*packagePath = package.absolutePath();
return true;
}

static uint32_t
parseRole(const QString &role)
{
Expand Down Expand Up @@ -76,7 +291,7 @@
parser.addOption(outputNameOption);
QCommandLineOption pathOption(
QStringLiteral("path"),
QStringLiteral("Wallpaper path (image or video)."),
QStringLiteral("Wallpaper path (image, video, or fragment shader)."),
QStringLiteral("file-path")
);
parser.addOption(pathOption);
Expand Down Expand Up @@ -149,6 +364,27 @@
wallpaper->set_image_source(wallpaperPath, role);
} else if (isVideoFile(wallpaperPath)) {
wallpaper->set_video_source(wallpaperPath, role);
} else if (isFragmentShaderFile(wallpaperPath)) {
if (wallpaper->version() < TreelandWallpaperManagerV1::InterfaceVersion) {
qCritical() << "Compositor wallpaper protocol version is" << wallpaper->version()
<< "but shader wallpaper requires version"
<< TreelandWallpaperManagerV1::InterfaceVersion
<< ". Restart treeland with the updated treeland-protocols and compositor build.";
delete wallpaper;
delete wallpaperManager;
qApp->exit(EXIT_FAILURE);
return;
}

QString packagePath;
if (!ensureShaderPackage(wallpaperPath, &packagePath)) {
delete wallpaper;
delete wallpaperManager;
qApp->exit(EXIT_FAILURE);
return;
}

wallpaper->set_shader_source(packagePath, role);
} else {
qCritical() << "Unsupported wallpaper file type:" << wallpaperPath;
delete wallpaper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class TreelandWallpaperManagerV1
{
Q_OBJECT
public:
static constexpr int InterfaceVersion = 1;
static constexpr int InterfaceVersion = 2;
explicit TreelandWallpaperManagerV1();
~TreelandWallpaperManagerV1() override;

Expand Down
16 changes: 16 additions & 0 deletions src/modules/wallpaper/wallpapermanagerinterfacev1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class TreelandWallpaperInterfaceV1Private : public QtWaylandServer::treeland_wal
void destroy(Resource *resource) override;
void set_image_source(Resource *resource, const QString &fileSource, uint32_t nativeRole) override;
void set_video_source(Resource *resource, const QString &fileSource, uint32_t nativeRole) override;
void set_shader_source(Resource *resource, const QString &fileSource, uint32_t nativeRole) override;
};

TreelandWallpaperInterfaceV1Private::TreelandWallpaperInterfaceV1Private(TreelandWallpaperInterfaceV1 *_q,
Expand Down Expand Up @@ -211,6 +212,17 @@ void TreelandWallpaperInterfaceV1Private::set_video_source([[maybe_unused]] Reso
Q_EMIT q->videoSourceChanged(workspace->currentIndex(), fileSource, roles);
}

void TreelandWallpaperInterfaceV1Private::set_shader_source([[maybe_unused]] Resource *resource,
const QString &fileSource,
uint32_t nativeRole)
{
TreelandWallpaperInterfaceV1::WallpaperRoles roles =
static_cast<TreelandWallpaperInterfaceV1::WallpaperRoles>(nativeRole);
Workspace *workspace = Helper::instance()->workspace();
Q_ASSERT(workspace);
Q_EMIT q->shaderSourceChanged(workspace->currentIndex(), fileSource, roles);
}

TreelandWallpaperInterfaceV1::TreelandWallpaperInterfaceV1(WOutput *output,
const QString &userName,
wl_resource *resource,
Expand Down Expand Up @@ -249,6 +261,10 @@ void TreelandWallpaperInterfaceV1::sendError(const QString &source, Error error)

void TreelandWallpaperInterfaceV1::sendChanged(WallpaperRoles roles, WallpaperType type, const QString &source)
{
if (type == Shader && wl_resource_get_version(d->resource) < TreelandWallpaperManagerInterfaceV1::InterfaceVersion) {
return;
}

d->send_changed(roles, type, source);
}

Expand Down
6 changes: 5 additions & 1 deletion src/modules/wallpaper/wallpapermanagerinterfacev1.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class TreelandWallpaperManagerInterfaceV1 : public QObject , public WServerInter

QByteArrayView interfaceName() const override;

static constexpr int InterfaceVersion = 1;
static constexpr int InterfaceVersion = 2;

Q_SIGNALS:
void wallpaperCreated(TreelandWallpaperInterfaceV1 *interface);
Expand Down Expand Up @@ -52,6 +52,7 @@ class TreelandWallpaperInterfaceV1 : public QObject
enum WallpaperType {
Image = 0,
Video = 1,
Shader = 2,
};

enum WallpaperRole {
Expand Down Expand Up @@ -79,6 +80,9 @@ class TreelandWallpaperInterfaceV1 : public QObject
void videoSourceChanged(int workspaceIndex,
const QString &fileSource,
WallpaperRoles roles);
void shaderSourceChanged(int workspaceIndex,
const QString &fileSource,
WallpaperRoles roles);

private:
explicit TreelandWallpaperInterfaceV1(WOutput *output,
Expand Down
10 changes: 10 additions & 0 deletions src/modules/wallpaper/wallpapernotifierinterfacev1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ TreelandWallpaperNotifierInterfaceV1::TreelandWallpaperNotifierInterfaceV1(QObje
void TreelandWallpaperNotifierInterfaceV1::sendAdd(TreelandWallpaperInterfaceV1::WallpaperType type, const QString &fileSource)
{
for (const auto &resource : d->resourceMap()) {
if (type == TreelandWallpaperInterfaceV1::Shader
&& resource->version() < TreelandWallpaperNotifierInterfaceV1::InterfaceVersion) {
continue;
}

d->send_add(resource->handle, type, fileSource);
}
}
Expand Down Expand Up @@ -84,5 +89,10 @@ QByteArrayView TreelandWallpaperNotifierInterfaceV1::interfaceName() const

void TreelandWallpaperNotifierInterfaceV1::sendAddForResource(wl_resource *resource, TreelandWallpaperInterfaceV1::WallpaperType type, const QString &fileSource)
{
if (type == TreelandWallpaperInterfaceV1::Shader
&& wl_resource_get_version(resource) < TreelandWallpaperNotifierInterfaceV1::InterfaceVersion) {
return;
}

d->send_add(resource, type, fileSource);
}
2 changes: 1 addition & 1 deletion src/modules/wallpaper/wallpapernotifierinterfacev1.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class TreelandWallpaperNotifierInterfaceV1 : public QObject , public WServerInte

QByteArrayView interfaceName() const override;

static constexpr int InterfaceVersion = 1;
static constexpr int InterfaceVersion = 2;

void sendAddForResource(wl_resource *resource, TreelandWallpaperInterfaceV1::WallpaperType type, const QString &fileSource);
void sendAdd(TreelandWallpaperInterfaceV1::WallpaperType type, const QString &fileSource);
Expand Down
Loading
Loading