Wayland 壁纸层上的音乐律动可视化(GPU 渲染,wgpu/Vulkan)。
┌─────────────────────────────────────────────┐
│ Lua 脚本层(怎么工作) │
│ 粒子/音频幅度/运动/条件逻辑/动态调参 │
└──────────────┬──────────────────────────────┘
│ 每帧调用
┌──────────────▼──────────────────────────────┐
│ Rust 内核 │
│ ├─ 音频:PipeWire monitor → FFT → 128 频段 │
│ ├─ 配置:QML 解析 → Config │
│ ├─ 渲染:wgpu (Vulkan) → wl-layer-shell │
│ ├─ 壁纸:图片/视频/网页场景 + 50 种 GLSL 过渡│
│ └─ Widgets:时钟/封面/频谱/圆环/粒子/歌词 │
└─────────────────────────────────────────────┘
- QML(
pulse-ring.qml):只负责静态样式——形状、颜色、大小、位置、widget 布局 - Lua(
pulse-ring.lua):负责所有动态行为——粒子(轨道/速度)、音频条幅度、主环运动、衰减/平滑、自转、空闲呼吸、夜间模式、频段变换
- 多重圆环:外环(频段律动)/ 中环(整体能量)/ 内环(低频 bass)
- 形状系统:ring / square / diamond / hexagon / triangle / star / flower,旋转、虚线
- 星环效果:连续半透明环带 + 粒子环绕
- Widgets:模拟时钟、数字时钟、专辑封面(MPRIS 实时)、条形频谱(含镜像)、独立圆环、歌词(LRC 逐字卡拉OK),自由放置
- 壁纸引擎:静态图片(
imageWallpaper,cover/contain/stretch + 完整 mipmap 链)、视频壁纸(GStreamer 解码,音频可选)、网页/场景壁纸、多壁纸轮播,以及 50+ 种 GLSL 切换动画(fade/circleopen/crosszoom/glitchmemories…) - 网页音频 API:HTML/CSS/Canvas/WebGL 页面可实时接收 128 段频谱、整体能量和低/中/高频,驱动音效动画
- 内置网页预设:紫色音频粒子、渐变时钟、极光声场;清单见
assets/wallpapers/presets.json - 稳定离屏渲染:网页壁纸固定使用项目内锁定版本的 Electron,默认以 960×540、约 30fps 软件渲染并丢弃过期帧,避免系统 Electron 版本差异和帧队列堆积
- 歌词:本地
~/.config/pulse-ring/lyrics/*.lrc优先,在线回退 QQ 音乐 → Lrclib(时长校验防错歌)并缓存;跟随 MPRIS 播放进度,行级点亮高亮 + 上一/下一行预览 - Lua 脚本:
onUpdate/transformBands/pulse.*API,可逐帧调整配置和频段 - 原生插件:Rust 动态库可更新状态、变换频段并为
pluginwidget 渲染 RGBA 纹理 - 多显示器:每台独立渲染
- KDE Wayland 录屏兼容:使用 Bottom layer,保持位于普通窗口下方,同时避免录屏时被 Plasma 桌面背景覆盖
- 音频:PipeWire monitor 实时 FFT
# 手动安装:
git clone https://github.com/MEKCCK/pulse-ring
cd pulse-ring
npm --prefix electron-wallpaper install # 安装项目锁定的 Electron
cargo build --release
./target/release/pulse-ring基础依赖:Rust/Cargo、PipeWire、Wayland、Vulkan、Fontconfig 和 GStreamer。网页/场景壁纸还需要 Node.js/npm 在项目的 electron-wallpaper 目录安装锁定版本 Electron。歌词显示建议安装 JetBrains Maple Mono 或其他 CJK 字体。
pulse-ring 不会调用系统全局 Electron;如果项目内运行时缺失,会明确提示执行上面的 npm 安装命令。
网页壁纸运行时会从编译时的项目目录读取 electron-wallpaper 和 assets,因此使用网页预设时请保留源码检出目录,或通过包管理器安装完整资源,不要只单独复制可执行文件。
pulse-ring首次运行自动生成 ~/.config/pulse-ring/pulse-ring.qml + pulse-ring.lua(内置默认配置)。
// ~/.config/pulse-ring/pulse-ring.qml —— 静态样式
PulseRing {
shape: "ring"
colorMode: "gradient"
colors: ["#6750A4", "#7D5260", "#D0BCFF", "#EADDFF"]
widgets: [
Widget { type: "analog"; x: 0.5; y: 0.5; size: 0.13 },
Widget { type: "cover"; x: 0.82; y: 0.16; size: 0.14 },
Widget { type: "bars"; x: 0.5; y: 0.9; size: 0.55; bars: 36 },
Widget {
type: "lyric"; x: 0.5; y: 0.82; size: 0.7; fontSize: 42; showPrevNext: true
color: "#B8B4C8" // 上一/下一行(暗色)
colors: ["#EADDFF", "#FFD740"] // 当前行 / 卡拉OK进度色
}
]
}壁纸配置(壁纸引擎,三种类型任意混排):
wallpapers: [ // 轮播列表:图片 / 视频 / 网页(HTML) 可混排
"~/Videos/壁纸.mp4", // 视频壁纸(GStreamer 解码,可带声音)
"~/Pictures/壁纸.jpg", // 图片壁纸
"~/wallpapers/我的壁纸/index.html" // 网页壁纸(Electron 离屏渲染 HTML/CSS/JS)
]
wallpaperInterval: 12 // 轮换间隔(秒)
wallpaperTransition: 1.8 // 过渡时长(秒)
wallpaperTransitionEffect: "crosszoom" // 50+ 种过渡效果之一(fade/circleopen/glitchmemories…)
// 单张模式(不轮播):
imageWallpaper: "~/Pictures/壁纸.jpg" // 静态图(留空=透明)
imageWallpaperMode: "cover" // cover/contain/stretch
videoWallpaper: "~/Videos/壁纸.mp4" // 视频
videoWallpaperAudio: true // 视频是否出声
webWallpaper: "~/wallpapers/index.html" // 网页壁纸
sceneWallpaper: "~/wallpapers/aurora-scene" // 常驻场景,不参与轮播
wallpaperLayer: "auto" // 壁纸层:auto=KDE 用 bottom(防录屏覆盖),其余 background(透明窗口可见);可手动 background/bottomsceneWallpaper 用于持续运行的生活场景;配置后它优先于普通图片/视频轮播。webWallpaperSize 默认是 [960, 540],用于控制 Electron 离屏渲染分辨率。
壁纸打包(Wallpaper Engine 式:一个文件夹 = 一个壁纸):
my-wallpaper/
├── project.json # 清单
├── index.html # 网页/场景资源(或 video.mp4 / image.jpg)
├── style.qml # 可选:该壁纸专属的环样式(QML)
├── behavior.lua # 可选:该壁纸专属的行为脚本(Lua)
└── assets/... # 任意引用资源(HTML 相对路径直接引用)
{ "type": "image", "title": "我的壁纸",
"images": ["img-1.jpg", "img-2.jpg", "img-3.jpg"],
"interval": 60, "transition": 2, "transitionEffect": "fade",
"qml": "style.qml", "lua": "behavior.lua",
"params": { "baseHue": 262 } }- 安装到壁纸库:
pulse-ring --install-wallpaper my-wallpaper/→~/.config/pulse-ring/wallpapers/my-wallpaper/ - 配置按名字引用:
wallpapers: ["my-wallpaper"]/sceneWallpaper: "my-wallpaper"/imageWallpaper: "my-wallpaper" type:web/scene(HTML)、video(视频)、image(图片)- 壁纸包能力 ≥ 全局(包激活时覆盖全局设置):
images(多图轮换,无需file)、interval(轮换间隔)、transition(过渡时长)、transitionEffect(50 种过渡效果)、audio(视频声音) qml/lua:激活该壁纸时应用专属样式与行为params通过window.pulseRing.onConfig()传给页面
网页/场景壁纸通过 window.pulseRing.onAudio(callback) 获取 128 段频谱以及 energy/bass/mid/treble;该方法返回取消订阅函数。onBands 作为兼容别名保留,getAudioData() 可读取最新一帧,apiVersion 当前为 1。
const unsubscribe = window.pulseRing.onAudio(({ bands, energy, bass, mid, treble }) => {
// bands 是 Float32Array(128),数值已归一化
renderAudioFrame(bands, { energy, bass, mid, treble });
});
window.pulseRing.onConfig(params => applyWallpaperParams(params));
// 页面销毁或不再监听时:unsubscribe();仓库自带三套示例:
| ID | 路径 | 音频响应 | 说明 |
|---|---|---|---|
purple-particles |
assets/wallpapers/audio-scene |
是 | 紫色粒子、脉冲环和底部频谱 |
demo-clock |
assets/wallpapers/demo-clock.html |
否 | 渐变数字时钟和静态星点 |
aurora |
assets/wallpapers/aurora-scene |
是 | 多层极光、星空、透视网格和能量核心 |
歌词来源(按优先级):
- 本地文件
~/.config/pulse-ring/lyrics/<标题>.lrc或<歌手> - <标题>.lrc - 缓存
~/.cache/pulse-ring/lyrics/(在线获取成功后自动保存) - 在线获取:QQ 音乐(时长校验防错歌)→ Lrclib,按 MPRIS 的标题/歌手自动匹配
歌词 widget 参数:fontSize(当前行字号)、color(上一/下一行颜色)、colors[0/1/2](当前行渐变:起始/中间/高光色)、showPrevNext(是否显示上一/下一行)
-- ~/.config/pulse-ring/pulse-ring.lua —— 动态行为
function onUpdate(dt)
config.growth = 0.14 + ring_amp * 0.12
pulse.setWidget(2, "barHeight", 0.04 + energy * 0.16)
end
function transformBands(bands) ... endpkill pulse-ring
AGPL-3.0-only © MEKCCK,详见 LICENSE。
本项目(渲染优化与歌词功能)在设计上参考/借鉴了以下开源项目,特此致谢并遵循其许可证:
- Folia(AGPL-3.0)—— 歌词管线(LRC 解析、逐字着色、行级高亮)、主题与视觉设计参考
- SPlayer(AGPL-3.0)—— 网络歌词多源获取、时长匹配校验、歌词元数据/署名行过滤思路
- Kaleidux(AGPL-3.0)—— 壁纸引擎:视频壁纸(GStreamer playbin/appsink)、50+ GLSL 切换动画库(gl-transitions)、mipmap 生成功能与过渡效果灵感
pulse-ring 采用 AGPL-3.0-only(与所参考项目 Folia/SPlayer/Kaleidux 的 AGPL-3.0-only 完全对齐;兼容原 GPL-3.0 条款);引用来源均以概念/思路形式再实现于 Rust/JS/Cargo 代码中。
- 帧率:始终 30fps(
PULSE_RING_MAX_FPS=60可开 60fps),空闲动画(呼吸/自转/粒子/时钟)保持流畅;若想省电,可显式设置PULSE_RING_IDLE_FPS=15在静止时降低更新率。 - 性能剖析:
PULSE_RING_PROFILE=1运行,每 60 帧输出各阶段耗时(pull_audio/lua/plugins/particles/widgets/render)