Repository navigation
Releases: azkbzxf/spw-folia
Release list
spw-folia v0.4.6 — 结构发现:不再跟着 SPW 的混淆名发版
spw-folia v0.4.6 — 结构发现:不再跟着 SPW 的混淆名发版
v0.4.6 是适配机制改造,功能与 v0.4.5 完全一致(更多菜单里的 Folia 项、播放控制、托盘菜单、
主题取色、透明层都不变)。变的是插件怎么找到宿主的那几个类:从「人工反编译提取混淆名、
写进 HostTargets.PROFILES、发插件版本」改成「解析宿主自己的字节码,按结构约束唯一发现目标」。
一句话:宿主下次 R8 改名不再需要发插件版本。
问题
SPW 每次发版都会重新跑 R8,把「更多」弹层相关的三个 Compose 类整个改名:
| 版本 | 菜单 facade | MenuFlyoutScope | PopupState |
|---|---|---|---|
| 1.19.0 | ē (U+0113) |
ন (U+09A8) |
Ⴈ (U+10A8) |
| 1.19.2 | घ (U+0918) |
Ը (U+0538) |
ะ (U+0E30) |
| 1.19.3 | ɦ (U+0266) |
त (U+0924) |
ל (U+05DC) |
v0.3.1 起混淆名已经从代码里挪进数据(PROFILES 表 + host-targets.json 缓存 + 一条「按形状
扫描」的兜底路径),所以用户不用每个版本都换插件。但维护者的负担没变:每次 SPW 发版,
仍然要人工解包 ffmpeg-x64.dll → vineflower 反编译 → 从 42,147 个 class 里认出那三个 →
抄码点 → 加 profile → 构建发版。v0.4.5 就是这么救回来的(1.19.3 改了三个类名,菜单项直接消失)。
这条路径还有两个结构性问题:
- 它是猜名字,不是查结构。 profile 对不对,只靠「拿反射试一下签名」,试不过就整条失效。
- 兜底的扫描路径太弱。 它只按「6 个参数、其中 4 个未被混淆的类型」认方法,遇到改名的
MenuFlyoutScope/PopupState还得再靠「找带默认参数 bridge 的接口」二次拆解;v0.4.5 那个
潜伏 bug(内部名/二进制名混用)也是这条路径的产物。
方案
参考 SPW-Lyrics(https://github.com/GaBoron/SPW-Lyrics,integration/hostui/
的 HostBytecodeIndex.java + HostPlayerMenuDiscovery.java,及其 docs/开发说明.md
「主窗口 Hook 与动态发现」一节):不读混淆名,读字节码结构。
新增两个文件(均在 spw-folia/src/main/kotlin/dev/spw/folia/):
| 文件 | 职责 |
|---|---|
HostBytecodeIndex.kt |
用 JDK 25 的 java.lang.classfile API 解析宿主 jar,建立「方法体 → 调用点 / 字段访问」的调用图。只读字节,从不加载或初始化宿主类。 |
HostMenuDiscovery.kt |
在索引上跑发现链,输出 6 个必需角色 + 2 个可选角色,全部是点分二进制名。 |
HostTargets 的解析链变成四层(详见 docs/hook-target.md「结构发现」一节):
| # | 层 | 何时 | 成本 |
|---|---|---|---|
| 1 | host-targets.json 缓存 |
同一版本第二次起 | ~0 |
| 2 | 内置 profile(1.19.3 / 1.19.2 / 1.19.0) | 已知版本 —— 回归基准 + 零成本快路径 | 实测 53–57 ms |
| 3 | 结构发现(主路径) | 未知版本 —— 后台线程 | 索引约 1.3–1.5 s,每版本一次,结果缓存 |
| 4 | 旧版 classpath scan() |
结构发现失败时的第二意见 | ~0.5–2 s |
每一层的结果都过同一道反射校验(validateMenu / validatePalette:方法签名、scope bridge、
调色板 getter)。结构发现的结果还会跟「仍然能校验通过的 profile」逐字段比对,一致就记
host structural matches regression baseline …,不一致记 WARNING: structural discovery disagrees with … 并把两边的名字与参数下标都打出来 —— 不静默取平均。
发现链每一步只写结构约束(PlaybackController#releaseAudioEngine 的调用者、BlurPopupMenu 的
参数形状、自指的 Kotlin 默认参数 bridge、builder 的参数锚点……),完整 10 步表格在
docs/hook-target.md。
为什么要收紧 SPW-Lyrics 的 builder 规则
SPW-Lyrics 原版的 builder 规则(static + 返回 kotlin.Unit + 参数里没有 Composer + 方法体调用了
item bridge)在 1.19.3 上有 15 个候选,对 SPW 自己的菜单结构约束太弱,照抄不可用。
v0.4.6 在同样四条之外,还要求参数表里同时出现六个锚点:Track、PiscesMediaItem、
Navigator、androidx.compose.runtime.State,加上两个由前面步骤推导出来的
L<scope>; 与 popupStateType。加完后 1.19.3 上唯一命中。
纪律:候选必须唯一
每一步都是 unique(...):候选数 != 1 就抛 IllegalStateException(消息带角色名 + 候选数量),
绝不 best-effort 挑一个。理由是不对称:在宿主里调错方法会静默污染弹层,而抛错只是「这一版
没有 Folia 项 + 一条可读日志」。异常被捕获、记日志、退回第 4 层 scan(),不会让插件启动失败。
可选角色(label / 调色板)走另一条纪律:失败降级为 null(菜单项有常量颜色兜底)。
验证
本轮是离线验证:真实宿主 dll + 编译后代码 + 双路径交叉校验 + 缓存往返。未做真机端到端
(没有把 .spmod 装进真实 SPW 里实际打开菜单确认 Folia 项出现)—— 这一项留给发布前的人工验收。
1. 索引规模与耗时(真实 1.19.3 宿主 app/ffmpeg-x64.dll)
宿主包是伪装成 dll 的 zip,111 MB、42147 个 class。预筛只解析引用了 Compose runtime 的类:
| 指标 | 实测 |
|---|---|
| 索引到的方法体 | 28058(约 2.8 万) |
| 索引耗时 | 1313 ms(多次运行区间约 1.3–1.5 s) |
| 发现本身(索引建好之后) | 约 70–92 ms |
| 已知版本走 profile(对照) | 53–57 ms |
2. 双路径交叉校验:8/8 完全一致
清掉缓存与内存态后分别跑两条路径,逐字段比对:
| 字段 | profile 路径(quickLoad) |
结构发现路径(deepResolve) |
|---|---|---|
menuClass |
androidx.compose.ui.ɦ |
相同 |
addItemsMethod |
Ϳ |
相同 |
scopeClass |
androidx.compose.ui.त |
相同 |
scopeBridge |
Ϳ |
相同 |
popupStateClass |
androidx.compose.ui.ל |
相同 |
popupDismiss |
ԩ |
相同 |
scopeParameter |
5 | 5 |
popupParameter |
2 | 2 |
码点断言 6/6 PASS:U+0266 / U+037F / U+0924 / U+037F / U+05DC / U+0529。
结构发现(1384 ms)与 profile(53–57 ms)解出的目标逐字符一致,这就是「结构发现没有退化」的
回归判据。
3. 真实日志
host structural index: com.xuncorp.pisces.PiscesMediaItem: 28058 method bodies (1313ms)
host structural matches regression baseline profile:1.19.3
host menu structural: androidx.compose.ui.ɦ.Ϳ(Track, PiscesMediaItem, ל, Navigator, State, त) scope=androidx.compose.ui.त.Ϳ (1313ms, 28058 bodies)
host palette structural: androidx.compose.ui.ʹ.Ԩ() via androidx.compose.ui.ʰ.Ԩ
未知版本匹配不到 profile 时,start() 打的是
host build <ver> matched no known profile; indexing the host bytecode in the background。
4. 缓存往返
host-targets.json(1597 bytes)写入后 reset() 再读,origin 正确保留为 structural,
structural 块带 indexMs / bodies / 每角色 candidateCounts(本轮 11 个角色全部 = 1)/
9 行 diagnostics。
5. 构建与发布包
./gradlew compileKotlin通过;./gradlew plugin通过- Gradle 产物
spw-folia/build/libs/plugin-dev.spw.folia-0.4.6.spmod
sha256136821fbe4b41f13e172d1b91420346886a28da6ffc662d9b63961ac13d3ce8a - 发布包
dist/release/spw-folia-0.4.6.spmod(12,476,192 B / 11.90 MB)
sha256fc06fd0724a4a6e8785bd912217c88c228aa1c234437d94e750c59a5f0619805 - 本次是兼容性发布:用
packaging/tools/splice-compat-release.py以已发布的
spw-folia-0.4.5.spmod为基线,只替换classes/。脚本断言 3832 个非classes/条目
(web/、native/、licenses/等)CRC 全部未变,丢弃 67 个旧 class、新增 85 个 node packaging/tools/verify-layout.mjs通过(10 项 checks 全 true)- Manifest:
Plugin-Version: 0.4.6、Plugin-Permissions: class-transform,library-read,key-bindings - 产物内含
HostBytecodeIndex*/HostMenuDiscovery*,未打包 kotlin/androidx
顺带修掉的两个问题
这两个都是「结构发现要跑在后台线程」这个新时序带出来的:
- 调色板反射的时序 bug(
FoliaMenuItem.kt)。 原先是两个by lazy:未知版本上
HostTargets.palette要等后台发现跑完才有值,而弹层可能在启动后几毫秒内就被打开 —— lazy 第一
次看到null就把null永久缓存了,于是即使 palette 之后解析成功,菜单项仍然一直是硬编码
的深色兜底色(浅色弹层上不可见)。改成按HostTargets.palette实例失效的缓存:每个解析出的
目标只反射一次,目标晚到或换版本都能跟上。 - hook 取参不再写死下标(
FoliaPlugin.kt)。 原先args[5](scope)/args[2](popupState)。
现在读MenuTarget.scopeParameter/.popupParameter(1.19.x profile 仍是 5/2,结构发现从
builder 参数表推导),并且用前先做界内检查 —— 参数表被重排时记
after: argument indices out of range …,而不是把错误的参数喂给注入逻辑。
兼容性
- 1.19.0 / 1.19.2 / 1.19.3 行为不变:三个 profile 全部保留,
quickLoad仍走 profile(实测
53–57 ms),只是它们的角色从「适配手段」变成「回归基准 + 首启动加速」。 - 版本门槛不变:
HostTargets.Version.MIN_SUPPORTED仍是 1.19.0,低于它照旧拒绝注入并记日志。 - 播放控制反射、托盘菜单、主题取色、透明层均未改动。
- 协议、设置格式、web bundle、native helper 均未改动 —— 可直接覆盖升级 v0.4.5。
host-targets.json格式向后兼容:旧缓存(没有structural/paletteTarget块)仍能读;
新缓存多出的字段只有诊断用途。调色板用paletteTarget这个键而不是palette,是因为项目自带的
Json.rawField不做层级区分、只取首个文本匹配,而structural.candidateCounts里也有一个
palette键(候选计数),同名会读出数字而不是对象。
升级说明
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.6.spmod。 - 授予
class-transform与library-read。 - 重启 SPW,
更多 → Folia。
可直接覆盖升级 v0.4.5。首次在未知宿主版本上启用时,后台会花约 1.3–1.5 s 建索引 —— 期间宿主
UI 不受影响(后台线程),结果写入 host-targets.json,之后每次启动零成本。已知版本(1.19.x)
完全没有这个等待。
已知限制
- 结构发现能适应重命名,不能适应页面结构重构。 宿主若不再用
BlurPopupMenu这类弹层
(发现链第 2 步的形状变了),或者「更多」弹层不再从Track/PiscesMediaItem取数
(builder 的锚点没了),结构约束就会失效:候选不唯一或为零 → 抛错 → 退回旧扫描 → 仍失败则记
could not locate the 更多 flyout,Folia 项缺失,其余功能不受影响。这种情况需要重新适配,
不是自动就能活的(SPW-Lyrics 的docs/常见问题.md也承认同一条边界)。 - 首次在未知版本上启用会有一次约 1.3–1.5 s 的后台索引(见上)。
- 其余限制与 v0.4.5 相同:色键透明(
rgb(255,0,255))、仅主屏全屏、monet与浅色主题下的
lumiere在透明下不可用、WebView2 进程偶发退出暂无自动重试。
安装
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.6.spmod。 - 授予
class-transform与library-read(key-bindings用于快捷键,可选)。 - 重启 SPW,
更多 → Folia。
可直接覆盖升级 v0.4.5。
校验
文件 spw-folia-0.4.6.spmod
大小 12,476,192 bytes (11.90 MB)
sha256 fc06fd0724a4a6e8785bd912217c88c228aa1c234437d94e750c59a5f0619805
发布包以已发布的 v0.4.5 为基线,仅替换 classes/(web/、native/、licenses/ 逐字节未变)。
本次改动
- 新增
HostBytecodeIndex.kt、HostMenuDiscovery.kt;改HostTargets.kt、FoliaPlugin.kt、FoliaMenuItem.kt - 参考项目:GaBoron/SPW-Lyrics
spw-folia v0.4.5 — 适配 Salt Player 1.19.3:更多菜单里的 Folia 选项恢复
spw-folia v0.4.5 — 适配 Salt Player 1.19.3:更多菜单里的 Folia 选项回来了
v0.4.5 是一次宿主版本适配修复。SPW 更新到 1.19.3 后,迷你播放器「更多」弹层里注入的
Folia 选项消失了(其余功能一切正常)。根因是宿主 R8 混淆又一次重命名了我们 hook 的那三个类,
以及一个从 v0.3.1 起就潜伏在「按形状扫描」兜底路径里的 bug 被这次重命名暴露出来。
如果你不在 1.19.3 上,本版与 v0.4.3 行为完全一致,可以按需升级。
建议所有 SPW 1.19.3 用户升级:旧版本在这个宿主上「更多」菜单里没有 Folia 入口
(托盘右键和快捷键仍然可用,所以容易误以为插件整体正常)。
根因
SPW 每次发版都会重新混淆 PlayerMoreMenuFlyoutKt 及其相关的两个 Compose 类。历史映射:
| 版本 | 菜单 facade | MenuFlyoutScope | PopupState |
|---|---|---|---|
| 1.19.0 | ē (U+0113) |
ন (U+09A8) |
Ⴈ (U+10A8) |
| 1.19.2 | घ (U+0918) |
Ը (U+0538) |
ะ (U+0E30) |
| 1.19.3 | ɦ (U+0266) |
त (U+0924) |
ל (U+05DC) |
插件本应在未知宿主版本上走「结构扫描」兜底,但那条路径有一个潜伏 bug:
HostTargets.menuCandidates() 把 ParsedClass.name(this_class 的内部形式,用 / 分隔,
例如 androidx/compose/ui/ɦ)直接塞进了 MenuTarget.menuClass,而所有下游消费者
(validateMenu() 的 Class.forName()、hook 注册、缓存文件)期望的都是点分隔的二进制名。
于是 Class.forName() 抛 ClassNotFoundException,被 runCatching 吞成 false,扫描日志永远报
host scan found no flyout helper。
之前每个版本都有内置 profile,这条扫描路径从没被真正走到过,bug 一直没暴露。1.19.3 把三个类
全改名了,才让它现形——日志里能同时看到「扫描找到了候选」和「找不到 flyout」这种自相矛盾的输出:
host scan: 1 classpath entr(y/ies) [...], matching by shape
host scan found no flyout helper (1206ms) (classes=42147 parsed=788 menuHits=1 paletteHits=3)
WARNING: could not locate the 更多 flyout in 1.19.3; the Folia menu item will be missing
注意 menuHits=1:扫描其实找到了,只是验证阶段失败。
修复
- 新增 1.19.3 profile(
HostTargets.PROFILES,置于最前):ɦ/ל/त;helper 与
scope bridge 仍是Ϳ,popup dismiss 仍是ԩ(U+0529)。 - 修掉扫描路径的名字格式 bug:
menuClass = cls.name.replace('/', '.')。
这修的不只是 1.19.3 —— 未来任何未知宿主版本现在都能真正靠扫描兜底,不必每次都跟着发插件版本。
验证
- 用真实 1.19.3 宿主
app/ffmpeg-x64.dll(42147 个 class 的多语言 zip)跑编译后的
HostTargets:quickLoad命中profile:1.19.3,deepResolve扫描路径独立解析出完全相同
的目标(origin=scan),两条路径交叉一致;host-targets.json缓存往返正常。 - 装进真实 Steam 版 SPW 后重启,日志确认:
host menu profile:1.19.3: androidx.compose.ui.ɦ.Ϳ(Track, PiscesMediaItem, ל, Navigator, State, त)
hookMethod installed: ... (profile:1.19.3)
Folia item appended (scope=androidx.compose.ui.গ$Ϳ, bridge=Ϳ)
- 打包布局验收
node packaging/tools/verify-layout.mjs:PASS,10/10 检查通过,
0 console error,3 canvases,settings island 注入与开关、ESC 行为、closed{reason:user}正常。
兼容性
- 1.19.0 / 1.19.2 的 profile 保留,行为不变。
- 播放控制反射、托盘菜单、主题取色、透明层在 1.19.3 上均未受影响——独立审计逐个核对了
PlaybackController、PlaybackQueueState、PlaybackQueue$Mode、队列元素类、PiscesMediaItem、
StateFlow.getValue、主题取色ʰ.Ԩ(composer).Ԩ()、compose-native-tray与 curtain 配置管理器,
1.19.3 与 1.19.2 完全一致。这也解释了为什么这次只有「更多」菜单一个入口消失。
安装
下载 spw-folia-0.4.5.spmod,放进 SPW「模组管理」导入即可(与旧版共存时请先卸载旧版)。
校验:
sha256: 3b2a74741cd4fc299e7ad0a0c57a296545fb0025a6bcd81f29381307886e6237
size: 12,227,007 B (11.66 MiB)
本次为兼容性发布:web/ / native/ / licenses/ 与 v0.4.3 逐字节相同(3832 个 payload
条目 CRC 全部一致),只替换了 classes/,因此不会有渲染器或原生 helper 的意外变化。
spw-folia v0.4.3 — 按真实日志修掉 4 个日志缺陷
spw-folia v0.4.3 — 修掉三个已知问题:凝彩透明遮挡、逐字歌词不同步、日志与「出现问题」
v0.4.3 是一次修 issue + 可观测性更新,对应仓库里三个 open issue。三个问题的根因都不在渲染器本身,
而在「插件 → 宿主 → 页面」这条数据链的末端:
- #13 逐字歌词与实际不同步:插件一直在发 SPW 的真实逐字时轴(
cells),页面侧的 mapper 把它丢了,
于是所有逐字样式拿到的是「按字符数均分整行时长」的假时轴。 - #11 凝彩未完全适配透明窗口:v0.2.0 的透明覆盖只关了纸雾/网点,而片尾海报、段落擦除块、装饰、
镜头畸变这几层不读那个开关,在色键窗口下整屏遮住桌面。 - #12 增加日志功能:原来只有一份插件日志 + 一份宿主 stdout 镜像,缺环境自检、缺一键反馈、
页面自己的console.warn也进不了日志。
重点
- 逐字歌词恢复真实时轴:
cells[]→ Foliawords[],长音/长词不再提前或滞后;没有逐字时轴的
歌词(普通 LRC)仍按原来的均分兜底,行为不变 - 凝彩透明只剩歌词与线框:装饰、角标、段落转场、镜头畸变、用户图片层在透明期间全部关闭;
片尾海报(整屏不透明底板)在透明期间不再构建 - 「出现问题」按钮:SPW「模组管理 → 配置 → 支持与反馈」新增;播放页右上角也加了救生圈入口
- 环境自检:10 项插件侧检查 + 宿主侧原生环境报告,缺哪项说哪项,并给修复链接
- 一键反馈:环境齐全时打开已预填环境信息与日志的 GitHub issue 表单,同时打开日志文件夹并选中日志
- 日志更详细:页面
console.warn/info入日志、卡顿(>100 ms 帧)单独成行、可选「详细日志」开关
修复
#13 部分歌词样式显示的逐字歌词与实际不同步
根因:poc/settings-ui/src/useConfigPublisher.ts 的 toPublisherLyrics 只映射
startMs/endMs/main/sub/id,把协议 §1 里的 cells(SPW LyricsLine.Cell,绝对毫秒)整个丢掉了。
publisher/config.ts 的 buildLine 因此永远走 fallbackWords()——把一行时长按字符数均分。
读 line.words 的样式(逐字扫光、关键字着色、凝彩/花火的逐字入场等)于是按伪造时轴动画。
修复:cells 1:1 映射成 PublisherWord[](空文本 cell 是时间空隙,跳过);words 只在真的有
可用 cells 时才设置,缺失时保持原来的均分兜底。验收见 docs/archive/verification-v0.4.2.md §1。
#11 凝彩样式,未完全适配透明窗口
根因:transparencyCompat.ts 里 tempera 的覆盖只有 {showBlocks:false, postProcessGrain:0, postProcessVignette:0}。showBlocks 关掉的是 35% 纸雾 + 网点 + blocks 构图容器;不读这个开关的
图层仍在:片尾海报(不透明全幅底 + 不透明圆盘)、段落 block-wipe 擦除块(整屏不透明人字块)、
装饰/角标/字影/水印、镜头畸变后处理(整帧重采样,截图里那道大弧线)、用户图片层。
修复:
- 覆盖扩到 8 个键:
showBlocks/showDecor/showCornerMarks/enableTransitions全关,
postProcessGrain/postProcessVignette/postProcessLensDistortion归零,layerImageFrequency:0。 - 片尾海报没有 tuning 开关,唯一杠杆是它的构建输入:
buildConfig在「透明 + tempera」时不发布
song/songArtist/songAlbum(suppressesClosingPoster),hasTemperaCreditsMetadata返回 false,
海报不建。
覆盖仍然只写进发布的 config、不写回设置存储,所以关掉透明后用户自己的 tuning 与片尾海报立刻回来。
#12 增加日志功能
新增「出现问题」入口:SPW 配置页按钮(preference_config.json 的 type:"button",
on_click: dev.spw.folia.FoliaPlugin.onReportProblem)+ 播放页右上角救生圈按钮/面板。
环境自检(FoliaDiagnostics.kt):Windows/位数、.NET Framework 4.8(注册表 Release)、
WebView2 Runtime ≥86、5 个原生文件、web/index.html、settings-ui.js、插件目录可写、SPW ≥1.19.0、
class-transform/library-read/key-bindings 三项权限、图形会话。宿主 BuildEnvReport() 补上
实际 WebView2 版本、.NET Release、nativeFiles.present/missing、用户数据目录、DPI、透明状态、音频源、
fps/状态计数与日志路径。缺失项逐个 toast/标红并给修复链接。
一键反馈:环境齐全时打开已预填的 GitHub issue 表单(环境表 + 页面状态 + 日志尾部),同时
explorer /select 打开日志目录并选中 spw-folia.log。宿主只接受 http/https。
日志增强:
Shim.js也镜像console.warn/console.info(此前只有console.error;info 去重且上限 200 条)- rAF 表新增
stallFrames(>100 ms 帧)与worstFrameMs,宿主写一行page-stall - 新配置开关「播放页 → 详细日志」→ 宿主
--verbose-log(导航行 + verbose 级别) - 新宿主参数
--log-file,让环境报告能指向插件镜像的宿主日志
详见 docs/logging-and-diagnostics.md。
兼容性
- 协议只新增消息,向后兼容:页面 → 宿主
envCheck/openLogFolder/openUrl,宿主 → 页面
envReport(见docs/protocol.md§3.1/§3.2/§3.5)。旧宿主不认这些消息,面板会退化成
「没有收到辅助进程的环境报告」并只开日志目录。 - 插件版本 0.4.1 → 0.4.2;SPW 仍要求 1.19.0+,权限不变。
- 设置树不变(
verboseLog是新键,缺省 false)。
升级
覆盖安装 .spmod 即可。升级后建议先点一次「模组管理 → 配置 → 支持与反馈 → 出现问题」,
确认环境自检全绿——顺便验证新面板在你的机器上是通的。
证据
docs/archive/verification-v0.4.2.md:三个修复各自在真实宿主 + 真实 WebView2 上的验收输出,
外加 packaging/tools/verify-layout.mjs 的 11/11 通过(fps 中位 116、console 错误 0)。
v0.4.3 追加:发布后日志复查
v0.4.2 发布后按真实会话日志逐行核对,发现 4 个问题(其中 3 个是本版新代码引入的),已在 v0.4.3 修掉:
console.warn只剩堆栈、没有正文 —— PixiJS 的弃用警告是三段式
groupCollapsed('%c…%c%s', …)+warn(stack)+groupEnd(),只镜像warn就把正文(第一条)丢了。
现在也镜像 group 三件套,并实现%s/%c占位符:标签单独成行,组内堆栈带标签:前缀。- 「打开日志文件夹」打不开 —— 面板传的是日志文件路径(
--log-file),宿主当目录用,
于是selected=False。现在文件 → 选中所属目录里的它;目录 → 选中其中的spw-folia.log。 catcher drag=on/off刷屏 —— 热区内每次按下/松开各一行、级别是info,一轮会话刷了 104 行。
降为debug。RasterizationScale从未设置 ——docs/protocol.md§8 一直要求按目标屏 DPI 设置它,代码只设了
Bounds。实测该值默认已由初始窗口尺寸推出并恰好等于 1.802(3200/1.802≈1776、2000/1.802≈1110),
所以本次画面并不模糊;但换到不同 DPI 的显示器后不会更新。现在在nav-done与每次几何变化后对齐
DpiForWindow,值不变时不打日志。
关于 dpr=1 的结论:默认与 --force-dsf 1 两组逐像素梯度能量 3.183 vs 3.049(基本相同)、
capture 尺寸都在 3200×2000 级别——画面是原生分辨率渲染,dpr=1 只是 WebView2 用 RasterizationScale
在合成层缩放的正常表现,不是缺陷。详见 docs/archive/verification-v0.4.2.md §6。
安装 / 升级
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.3.spmod。 - 授予
class-transform、library-read与key-bindings(漏授可在插件权限页补授后重启)。 - 重启 SPW,
更多 → Folia。
可直接覆盖升级 v0.4.2 / v0.4.1 / v0.4.0 / v0.3.x:协议只新增消息、设置格式未变
(verboseLog 是新键,缺省 false)。
校验
spw-folia-0.4.3.spmod
size 12,224,037 B (11.66 MiB)
sha256 a2015b34472477b8a06d73527df6db3d4a6f589458c909af6f41442a4efc513f
已知限制
- 凝彩的片尾海报在透明模式下被隐藏而不是重绘成透明版本(片尾是「平涂底板 + 不透明圆盘」,
去掉底板后标题可读性没有保障);关掉透明后海报照常出现。 - 详细日志只影响下一次打开 Folia(开关在 spawn 时读取),页面上没有即时切换入口。
page-stall的阈值(100 ms)与 fps 窗口(1 s)是经验值,更细的卡顿画像需要接PerformanceObserver。- 智能动感模式(smart beat)仍不可用(与 v0.4.0–v0.4.2 相同)。
spw-folia v0.4.2 — 凝彩透明遮挡 · 逐字歌词不同步 · 日志与「出现问题」
spw-folia v0.4.2 — 修掉三个已知问题:凝彩透明遮挡、逐字歌词不同步、日志与「出现问题」
v0.4.2 是一次修 issue + 可观测性更新,对应仓库里三个 open issue。三个问题的根因都不在渲染器本身,
而在「插件 → 宿主 → 页面」这条数据链的末端:
- #13 逐字歌词与实际不同步:插件一直在发 SPW 的真实逐字时轴(
cells),页面侧的 mapper 把它丢了,
于是所有逐字样式拿到的是「按字符数均分整行时长」的假时轴。 - #11 凝彩未完全适配透明窗口:v0.2.0 的透明覆盖只关了纸雾/网点,而片尾海报、段落擦除块、装饰、
镜头畸变这几层不读那个开关,在色键窗口下整屏遮住桌面。 - #12 增加日志功能:原来只有一份插件日志 + 一份宿主 stdout 镜像,缺环境自检、缺一键反馈、
页面自己的console.warn也进不了日志。
重点
- 逐字歌词恢复真实时轴:
cells[]→ Foliawords[],长音/长词不再提前或滞后;没有逐字时轴的
歌词(普通 LRC)仍按原来的均分兜底,行为不变 - 凝彩透明只剩歌词与线框:装饰、角标、段落转场、镜头畸变、用户图片层在透明期间全部关闭;
片尾海报(整屏不透明底板)在透明期间不再构建 - 「出现问题」按钮:SPW「模组管理 → 配置 → 支持与反馈」新增;播放页右上角也加了救生圈入口
- 环境自检:10 项插件侧检查 + 宿主侧原生环境报告,缺哪项说哪项,并给修复链接
- 一键反馈:环境齐全时打开已预填环境信息与日志的 GitHub issue 表单,同时打开日志文件夹并选中日志
- 日志更详细:页面
console.warn/info入日志、卡顿(>100 ms 帧)单独成行、可选「详细日志」开关
修复
#13 部分歌词样式显示的逐字歌词与实际不同步
根因:poc/settings-ui/src/useConfigPublisher.ts 的 toPublisherLyrics 只映射
startMs/endMs/main/sub/id,把协议 §1 里的 cells(SPW LyricsLine.Cell,绝对毫秒)整个丢掉了。
publisher/config.ts 的 buildLine 因此永远走 fallbackWords()——把一行时长按字符数均分。
读 line.words 的样式(逐字扫光、关键字着色、凝彩/花火的逐字入场等)于是按伪造时轴动画。
修复:cells 1:1 映射成 PublisherWord[](空文本 cell 是时间空隙,跳过);words 只在真的有
可用 cells 时才设置,缺失时保持原来的均分兜底。验收见 docs/archive/verification-v0.4.2.md §1。
#11 凝彩样式,未完全适配透明窗口
根因:transparencyCompat.ts 里 tempera 的覆盖只有 {showBlocks:false, postProcessGrain:0, postProcessVignette:0}。showBlocks 关掉的是 35% 纸雾 + 网点 + blocks 构图容器;不读这个开关的
图层仍在:片尾海报(不透明全幅底 + 不透明圆盘)、段落 block-wipe 擦除块(整屏不透明人字块)、
装饰/角标/字影/水印、镜头畸变后处理(整帧重采样,截图里那道大弧线)、用户图片层。
修复:
- 覆盖扩到 8 个键:
showBlocks/showDecor/showCornerMarks/enableTransitions全关,
postProcessGrain/postProcessVignette/postProcessLensDistortion归零,layerImageFrequency:0。 - 片尾海报没有 tuning 开关,唯一杠杆是它的构建输入:
buildConfig在「透明 + tempera」时不发布
song/songArtist/songAlbum(suppressesClosingPoster),hasTemperaCreditsMetadata返回 false,
海报不建。
覆盖仍然只写进发布的 config、不写回设置存储,所以关掉透明后用户自己的 tuning 与片尾海报立刻回来。
#12 增加日志功能
新增「出现问题」入口:SPW 配置页按钮(preference_config.json 的 type:"button",
on_click: dev.spw.folia.FoliaPlugin.onReportProblem)+ 播放页右上角救生圈按钮/面板。
环境自检(FoliaDiagnostics.kt):Windows/位数、.NET Framework 4.8(注册表 Release)、
WebView2 Runtime ≥86、5 个原生文件、web/index.html、settings-ui.js、插件目录可写、SPW ≥1.19.0、
class-transform/library-read/key-bindings 三项权限、图形会话。宿主 BuildEnvReport() 补上
实际 WebView2 版本、.NET Release、nativeFiles.present/missing、用户数据目录、DPI、透明状态、音频源、
fps/状态计数与日志路径。缺失项逐个 toast/标红并给修复链接。
一键反馈:环境齐全时打开已预填的 GitHub issue 表单(环境表 + 页面状态 + 日志尾部),同时
explorer /select 打开日志目录并选中 spw-folia.log。宿主只接受 http/https。
日志增强:
Shim.js也镜像console.warn/console.info(此前只有console.error;info 去重且上限 200 条)- rAF 表新增
stallFrames(>100 ms 帧)与worstFrameMs,宿主写一行page-stall - 新配置开关「播放页 → 详细日志」→ 宿主
--verbose-log(导航行 + verbose 级别) - 新宿主参数
--log-file,让环境报告能指向插件镜像的宿主日志
详见 docs/logging-and-diagnostics.md。
兼容性
- 协议只新增消息,向后兼容:页面 → 宿主
envCheck/openLogFolder/openUrl,宿主 → 页面
envReport(见docs/protocol.md§3.1/§3.2/§3.5)。旧宿主不认这些消息,面板会退化成
「没有收到辅助进程的环境报告」并只开日志目录。 - 插件版本 0.4.1 → 0.4.2;SPW 仍要求 1.19.0+,权限不变。
- 设置树不变(
verboseLog是新键,缺省 false)。
升级
覆盖安装 .spmod 即可。升级后建议先点一次「模组管理 → 配置 → 支持与反馈 → 出现问题」,
确认环境自检全绿——顺便验证新面板在你的机器上是通的。
证据
docs/archive/verification-v0.4.2.md:三个修复各自在真实宿主 + 真实 WebView2 上的验收输出,
外加 packaging/tools/verify-layout.mjs 的 11/11 通过(fps 中位 116、console 错误 0)。
安装 / 升级
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.2.spmod。 - 授予
class-transform、library-read与key-bindings(漏授可在插件权限页补授后重启)。 - 重启 SPW,
更多 → Folia。
可直接覆盖升级 v0.4.1 / v0.4.0 / v0.3.x:协议只新增消息、设置格式未变(verboseLog 是新键,
缺省 false,旧 config.json 不受影响)。
升级后建议点一次 模组管理 → 配置 → 支持与反馈 → 出现问题,确认环境自检全绿——顺便验证新面板
在你的机器上是通的。
校验
spw-folia-0.4.2.spmod
size 12,221,858 B (11.66 MiB)
sha256 5bfdf80df4a93d96c283a0823788cf8d2a5ffeaa470f4c4c1e89fe5114cbcedb
已知限制
- 凝彩的片尾海报在透明模式下被隐藏而不是重绘成透明版本(片尾是「平涂底板 + 不透明圆盘」,
去掉底板后标题可读性没有保障);关掉透明后海报照常出现。 - 自检项里的图形会话只把
SESSIONNAME=RDP-*标成软依赖警告(远程桌面下 WebGL 可能降级),
不计入「缺失环境」。 - 详细日志只影响下一次打开 Folia(开关在 spawn 时读取),页面上没有即时切换入口。
- 设置页的「出现问题」面板依赖宿主的新消息(
envCheck/envReport);只升级插件而不升级随包宿主
不会发生(两者同一.spmod),但旧版宿主配新版页面会退化成「没有收到辅助进程的环境报告」并只
打开日志目录。 - 智能动感模式(smart beat)仍不可用(与 v0.4.0/v0.4.1 相同):注入页没有节拍分析模型、宿主不发
beat事件,节拍驱动的效果保持静止。
spw-folia v0.4.1 — 真实预览:预览框就是播放页
spw-folia v0.4.1 — 真实预览:预览框就是播放页,预览文本选项移除
v0.4.1 是一次预览体验更新:设置页左侧的大预览不再播放预置的示例歌曲、也不再用设置页
“以为的”配置去拼一个近似预览,而是逐帧镜像播放页——真实曲目、真实歌词、真实主题与背景、
真实播放进度与音频,连「播放页透明背景」打开后的透明效果也一并真实呈现。字体 tab 里的
「预览文本(当前歌词 / 神文之诗 / 野芝麻)」整项移除,默认即预览当前歌词;相关设置字段与
快照字段一起删除。这一版还修掉了一个打包体积回归(Python 3.14 的 zip 压缩级别未生效)。
重点
- 真实预览:预览 iframe 直接消费播放页实际收到的 config,不再由设置页用占位歌重建
- 真实时间轴:暂停/继续冻结或恢复的是真实时钟;歌词动画与播放页同一帧,不再本地合成
- 透明背景真实呈现:播放页透明时预览框显示棋盘格 + 「播放页透明背景」角标,iframe 内歌词透明底
- 移除「预览文本」:整行控件、
previewPlaceholderId状态与快照字段、两首内置示例歌数据全部删除 - 打包体积修复:
.spmod从虚高的 12.38 MB 回到 12,191,035 字节(11.63 MiB)
新增 / 变更
-
预览框 = 播放页的镜像(新) —— v0.4.0 及以前,设置页用
previewSong.ts里的占位歌
(神文之诗 / 野芝麻)+/preview/placeholder_cover.jpg预设封面 +buildConfig()重建一份
预览配置,再塞进 iframe 合成一条本地时间轴。问题有三:占位歌不是用户的歌;设置页重建的配置
与播放页实际渲染的配置可能不一致(尤其宿主/插件直接下发 config 时);本地合成时间轴与真实
播放进度、音频频谱无关。
v0.4.1 改为:- config 唯一来源 = 播放页收到的 config:
Shim.js的deliver('config')在投递时记住
lastConfig并发出本地config事件;设置页usePlayerConfig()读bridge.lastConfig()并
持续订阅,把同一份对象postMessage给预览 iframe——样式、调参、背景、字体、字幕、
透明标记全部自动一致。 - 真实时钟与音频:设置页订阅桥的
state/audio事件(顶层 Shim 本来就在转发宿主
数据),把原始 payload 转发给 iframe;PreviewShim.js新增 live 模式,用与Shim.js
完全相同的映射转成 SSEclock/audio事件驱动 Folia 渲染器,不再本地合成。 - 暂停冻结真实时钟:暂停时用最后一帧 state 发
PAUSED把时钟钉住,恢复时重发最后一帧,
下一帧真实 state 到来即无缝接上;切到别的设置 tab 时预览继续跟随(不再被卸载重建)。 - 占位歌数据
previewSong.ts删除,仅保留currentLyric.ts给字体选择器的样例行使用;
预览框下方不再有预设背景色,透明时由设置页画棋盘格表示“这里会透出桌面”。
- config 唯一来源 = 播放页收到的 config:
-
移除「预览文本」选项(新) —— 字体 tab 不再有
当前歌词 / 神文之诗 / 野芝麻三段选择:
预览框永远是当前播放的歌词。实现上通过vite.config.ts的SETTINGS_SURGERY从上游
VisPlaygroundSettingsPanel.tsx移除 import、props、解构、重置调用与整行Row(5 个 marker,
上游改动会显式报错);previewPlaceholderId从SettingsState、store 默认值、设置快照
(snapshotFromState/applySnapshot)、packaging/testdata/default-settings.json与
poc/webview/server/compat-regression.mjs中一并删除。旧快照里该字段被忽略,不影响升级。 -
透明背景真实呈现(新) —— OBS 页面自身已把
html/body设为透明,因此 iframe 里的透明
配置可以直接透出下层:透明开启时预览框显示棋盘格 + 左下角「播放页透明背景」角标,歌词仍是
真实透明底渲染;关闭后恢复真实主题背景。设置页外层不再给预览铺任何预设颜色。 -
打包体积修复 ——
packaging/tools/assemble-release.py在 Python 3.14 下用显式ZipInfo
写 zip 时,ZipFile(compresslevel=9)不会传递到条目(3.14 改为ZipInfo.compress_level),
导致.spmod被按默认级别压缩、比上一版大约 190 KB。现在writestr(..., compresslevel=9)
显式传入,产物回到 12,191,035 字节(比 v0.4.0-preview.2 的 12,193,169 字节还小 2,134 字节)。
修复
- 预览与播放页不一致 —— 见「预览框 = 播放页的镜像」:不再用设置页重建配置。
- 预览框不是“当前歌曲” —— 占位歌与预设封面移除,空闲(播放页还没有 config)时预览框保持
空框而不是示例内容。 - 打包体积回归 —— 见上文「打包体积修复」。
验证
- 设置页构建:
cd poc/settings-ui && npx --no-install vite build→dist/settings-ui.js
2,237,591 字节(v0.4.0 为 2,241,477 字节)。 - bundle 组装:
node packaging/tools/stage-web.mjs→previewHtml: true、
previewShimCopied: true、indexHtmlPatched: true。 - 打包:
bash packaging/build-package.sh→ 3,837 文件 / 27,231,100 字节(web/24.89 MB、
native/1.03 MB、licenses/0.05 MB),与 v0.4.0 持平。 - 插件:
cd spw-folia && JAVA_HOME="C:/Program Files/Java/jdk-21.0.10" ./gradlew plugin -q --offline
→build/libs/plugin-dev.spw.folia-0.4.1.spmod205,694 字节,manifestPlugin-Version: 0.4.1、
Plugin-Has-Config: true、Plugin-Permissions: class-transform,library-read,key-bindings。 - 发布产物:
dist/release/spw-folia-0.4.1-preview.1.spmod12,191,035 字节
(sha2566e07ae30a3f2a1f92c165afa0cb516fa87b75a7484bc909690c8001532a6b7fb,3,896 条目;
manifestPlugin-Version: 0.4.1)。 - 打包后验收:
node packaging/tools/verify-layout.mjs→ PASS(10 项检查全绿:
exitedZero、closedUser、bridgePresent、settingsInjected、settingsAutoOpened、
closeSettingsCommand、openSettingsCommand、escClosedSettingsOnly、
visualizerCanvases、noConsoleErrors;fps 中位数 103.2,heap 峰值 75.6 MB,0 控制台错误)。 - 真实预览真机验收:
cd poc/settings-ui && node scripts/diag-real-preview.mjs(宿主真机 +
模拟插件 track/lyrics)→ 17/17 PASS:- 设置页打开、预览 iframe 挂载、子帧
ready、config 送达、shim 处于 live 模式; - 预览框显示真实曲目(Spike Track)与真实歌词(8 行,来自插件协议);
- iframe config === 播放页 config(同一份
lastConfig,含background.transparent); - 子帧时钟与宿主
posMs同步(如 19010 ms vs 19010 ms);子帧持续出帧、canvas 3 个; - 透明开关同时到达父级 wrapper(
st-stage-transparent+ 角标)与 iframe config; - 暂停后子帧时钟冻结(两次采样 24427 ms 不变,宿主仍在走),恢复后重新前进(28362 ms vs 28379 ms);
- 字体 tab 已无「预览文本」,且切 tab 后预览继续渲染。
- 截图:
poc/webview/host/out/real-preview/shots/preview-live.png、
preview-transparent.png、preview-fonts.png。
- 设置页打开、预览 iframe 挂载、子帧
- 文档:
docs/live-preview-iframe.md重写为 v0.4.1 契约;docs/known-inert-settings.md
记录「预览文本」移除与旧快照字段忽略。 - 版本号:
spw-folia/build.gradle.kts的version与PluginVersion均为0.4.1;Kotlin 侧
FoliaPlugin.PLUGIN_VERSION同步为0.4.1;install-local.sh默认版本号为0.4.1。
安装 / 升级
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.1-preview.1.spmod。 - 授予
class-transform、library-read与key-bindings(快捷键;漏授可在插件权限页补授后重启)。 - 重启 SPW,
更多 → Folia;设置页打开「歌词样式」即可看到真实预览。
可直接覆盖升级 v0.4.0 / v0.3.x(协议与设置格式均未变;旧快照里的 previewPlaceholderId
会被忽略)。
已知限制
- 预览框内是第二个完整 Folia 渲染器(iframe),会额外占用一份 GPU/CPU 与内存;打开设置页
期间属预期开销,关闭设置页即释放。 - 预览只在设置页可见时渲染(页面隐藏/最小化时暂停 rAF),不做后台运行。
- 播放页还没有收到 config 时(极短暂),预览框显示空框,不会回退到示例内容。
- 智能动感模式(smart beat)仍不可用:注入页没有节拍分析模型、宿主不发
beat事件,节拍驱动的
效果保持静止(与 v0.4.0 相同)。 - 系统字体列表不持久化(每次启动重新枚举),导入字体包存 IndexedDB(与 v0.4.0 相同)。
spw-folia v0.4.0 预览版 2 — 花火样式、随机样式池、自定义字体、托盘入口、透明快捷键、打开过渡动画
这是 v0.4.0 的第二个预览版(pre-release)。相比 preview.1,功能相同,安装包从 20.0 MB 缩到 12.2 MB(设置页资源去重 + 插图重新压缩)。
本地验收仍在进行中,正式版会在验收完成后发布。
spw-folia v0.4.0 — 花火样式、随机样式池、自定义字体、托盘入口、透明快捷键、打开过渡动画
v0.4.0 是一次内容 + 体验更新:web bundle 换成上游 Folia 0.7.22,带来第 15 种歌词样式
「花火」;同时补上随机歌词样式池、自定义字体(导入字体包 / 系统字体)、字体栏预览默认显示当前
歌词,以及最小化托盘图标的 Folia 入口;播放页透明背景也接进了 SPW 的快捷键管理,可以绑成自己的
快捷键。这一版还新增了从封面取色的打开过渡动画、非透明模式下的左上角退出按钮,并修掉了一批真机
验收中发现的问题(启动粉紫闪屏、预览框一直转圈、预览字体不跟随、托盘入口不显示 / 点了不关闭)。
issue #9 的启动成功提示噪音也在这一版移除;PR #10(多屏跟随、Alt 跨屏拖动、呼出快捷键可配置)
一并合入。
重点
- 上游 Folia 0.7.22:新歌词样式「花火」
- 随机歌词样式池 + 每首歌换一种 + 骰子重抽
- 自定义字体(导入字体包 / 系统字体),字体预览默认显示当前歌词,预览框字体实时跟随
- 托盘右键 Folia 入口(与「桌面歌词」同组,点击即开 / 关)
- 播放页透明背景快捷键两条(默认 Ctrl+Alt+G / Ctrl+Alt+Shift+G)
- 打开过渡动画:封面取色、透明→不透明淡入、默认关闭(配置页开关)
- 播放页左上角退出按钮(非透明模式)
- 修复:透明模式下打开时的粉紫闪屏、"正在连接 Folia"预览框、预览字体不跟随、托盘入口缺失
新增 / 变更
-
上游 Folia 0.7.22 适配 ——
poc/webview/dist/dist里的 web bundle 由 Folia 0.7.21 换成
0.7.22,取自Folia-Setup-0.7.22.exe,未做修改。 -
新歌词样式「花火」(hanabi) —— 第 15 种样式:日系歌词 PV 画风,歌词带色块逐字出场,烟花
沿字开花,镜头之间随升空烟花旋转上升,副歌亮起辉光;注册在 Folia 的 order 26,即绘光
(lumiere,25)与流光(classic,30)之间;设置页复用 Folia 自己的HanabiSettingsPanel,缩略图
poc/settings-ui/public/thumbs/hanabi.jpg。 -
随机歌词样式池 —— 「歌词样式」里可把 15 种样式的任意子集勾进随机池;打开「每首歌换一种」后
每首歌从池里抽一种且不连续重复;播放面板上的骰子按钮可当场重抽。随机池随设置快照保存。 -
自定义字体 —— 字体栏新增「自定义」磁贴,两条路都通:
- 导入字体包:
.ttf/.otf/.woff/.woff2,单个 ≤ 50 MB,存进 IndexedDB 并用
FontFace注册; - 系统字体:插件侧用 AWT 枚举字体家族,注入设置树的
systemFonts(最多 400 个家族)。
字重也一并恢复,选完字体可以继续调粗细。
- 导入字体包:
-
字体预览文本默认 = 当前播放歌词 —— 预览文本新增「当前歌词」选项并作为默认值,字体预览直接
渲染正在播放的那一行;空闲时回退到内置示例歌曲。随包的两首占位歌曲仍然可选。 -
字体预览跟随(修复) —— 设置页的「在线歌词预览」是嵌入的同源 iframe;此前导入字体只在播放器
主文档注册,iframe 文档里没有对应FontFace,所以预览始终回退系统字体。现在打开设置页时会把
已导入字体的字节注入预览 iframe 文档(在首次下发 config 之前完成,避免 Folia 渲染器不重排),
新导入字体也会再次注入并重发 config 触发重排;iframe 重新加载时自动重新注入。 -
托盘菜单 Folia 入口 —— SPW 最小化到托盘后,右键图标即可看到 Folia,位于「桌面歌词」之后、
同一分组内;点击是切换:窗口没开时打开,已经打开时关闭。
实现上除了 compose 层的菜单录制 hook,还增加了一层确定性原生补丁:hook
WindowsTrayMenuBuilderImpl.build()在返回的原生菜单项里强制插入 Folia 行,并在托盘管理器就绪后
主动刷新一次原生菜单 —— 这样不依赖 compose 的重录制时机。 -
播放页透明背景快捷键 —— SPW「快捷键管理」新增两条可重绑动作:
播放页透明背景:开 / 关(默认 Ctrl+Alt+G):切换透明背景;关闭时一并把「鼠标穿透」复位。播放页透明背景 + 鼠标穿透:开 / 关(默认 Ctrl+Alt+Shift+G):两者都关时一起打开,已经
全开时一起关闭。
窗口开着时按下立即生效:插件改设置树 → 宿主套色键 → 页面用自己的样式兼容表校验(
monet、
浅色主题下的lumiere等不支持透明的样式会拒绝并回退,此时提示「当前歌词样式不支持播放页透明
背景」);窗口没开时只写设置,下次打开直接以该模式启动。宿主原有的全局 Ctrl+Alt+B 逃生热键
保持不变;两条默认键位避开了宿主热键链(Ctrl+Alt+B / Y / P / Shift+B)和本机已被占用的
Ctrl+Alt+T。要让快捷键在 Folia 窗口聚焦时也生效,需要打开该动作的「全局」开关。 -
打开过渡动画(新) —— 每次打开 Folia 都播放,不区分普通 / 透明 / 鼠标穿透模式:
- 颜色从当前歌曲封面提取(HSV 聚类,最多 4 色;封面不可用时回退内置紫 / 粉 / 青 / 琥珀);
- 深色夜幕 + 极光光晕 + 漂浮火星 + 斜向扫光,居中
Folia字样,第二行「让歌词成为风景」使用
封面色的左→右渐变; - 动画从透明到不透明淡入,固定 0.8 秒;页面首帧就绪后淡出露出歌词页(整体时长受
WebView2 冷启动约 1.5–2 秒影响); - 多屏 / DPI 适配:按窗口所在显示器的物理边界摆放,并按该显示器 DPI(本机 180%)缩放火星半径、
文字粗细、细线等固定像素元素; - 配置页开关「打开过渡动画」,默认关闭;开关切换即时生效(下一次打开起)。
-
播放页左上角退出按钮 —— 未开启「播放页透明背景」时,播放页左上角显示圆形
×按钮(与右上角
设置齿轮严格对称、同样的玻璃质感与淡出行为),点击即关闭 Folia 窗口;透明模式自动隐藏。 -
PR #10:多屏跟随、Alt 跨屏拖动、呼出快捷键可配置 —— 已随本版合入(squash 为
b30df5d)。 -
安装包体积优化 —— 发布产物从首个预览版的 20.0 MB 降到 12.2 MB(−39%,比 v0.3.1 的
12.4 MB 还小一点),主要做了两件事:- 设置页资源去重:
web/settings/assets/里与web/assets/逐字节相同的 29 个图片(4.00 MB)不再
重复打包,引用改写成/assets/…;设置岛(注入的设置页)改为从 bundle 同源加载,根绝对路径
/assets/…才能正确解析; - 图片重压缩:插图 / 头像改为 256 色调色板 PNG,预览封面改为 progressive JPEG(
packaging/image-overrides/apply-image-overrides.mjs),4.67 MB → 1.39 MB;文件名与格式不变,所以不需要改任何动态资源 URL。
- 抵消项:上游 0.7.22 的 web 资源本身比 0.7.21 大 0.47 MB,插件类 +0.12 MB,宿主 +17 KB。
- 未压缩 staging 仍是 25.97 MB(主要是文本 JS,压缩进 zip 后只有 12.2 MB);
size-report.json
的 25 MB 预算仍是历史防膨胀阈值,现处于 OVER 状态,注释见docs/release.md。
- 设置页资源去重:
修复
- 透明 / 穿透模式打开时的粉紫闪屏 —— 宿主不再使用 WinForms
TransparencyKey(它会重建窗口句柄并
闪一帧未上色键的洋红底色),改为直接用 Win32 分层窗口 API(WS_EX_LAYERED+
SetLayeredWindowAttributes(LWA_COLORKEY)),并在设置洋红背景色之前装好色键。 - 过渡动画只在部分模式可见 —— 遮罩窗口与播放器窗口同为 TopMost,播放器后显示会盖住遮罩;现在在
窗口创建 / 显示 / 激活时都把遮罩重新置顶,普通模式同样能看到完整动画。 - 「正在连接 Folia」预览框 —— 开发安装的 bundle 曾直接拷贝原始 dist,其中 PWA 的
sw.js会把
/preview.html导航拦截回index.html,导致预览 iframe 停在 OBS 连接占位页;现在本地安装也统一
经packaging/tools/stage-web.mjs生成preview.html+_host/preview-shim.js(新增 gradle
stageDevWeb任务),并清理了旧的 service worker 缓存。 - 修改字体后预览框不跟随 —— 见上文「字体预览跟随」。
- 托盘右键看不到 Folia —— 见上文「托盘菜单 Folia 入口」的原生补丁。
- 托盘点 Folia 无法关闭 —— 托盘条目改为切换开关。
- 动画时长设置无实际效果 —— 打开时长受 WebView2 冷启动主导,滑杆差异不可感知;按验收反馈删除
时长设置,动画固定 0.8 秒,配置页只保留开关。 - issue #9:启动不再弹成功提示 —— 只有失败才提示。
验证
- 构建:
cd poc/settings-ui && npx --no-install vite build→dist/settings-ui.js
2,241,477 字节。 - 构建:
cd spw-folia && JAVA_HOME="C:/Program Files/Java/jdk-21.0.10" ./gradlew plugin -q --offline
→build/libs/plugin-dev.spw.folia-0.4.0.spmod(205,707 字节,manifestPlugin-Version: 0.4.0、
Plugin-Has-Config: true、Plugin-Permissions: class-transform,library-read,key-bindings)。 - 宿主:
bash poc/webview/host/build.sh→spw-folia-host.exe152,576 字节(含新的分层窗口
透明实现、IntroCurtain.cs,以及设置岛同源加载)。 - 打包:
bash packaging/build-package.sh→ 3,837 文件 / 25.97 MB(web/24.89 MB、native/
1.03 MB、licenses/0.05 MB);其中设置页资源去重 29 个文件、4.00 MB,图片覆盖 4.67 MB → 1.39 MB。 - 发布产物:
dist/release/spw-folia-0.4.0-preview.2.spmod12,193,169 字节
(sha2561c9816a992cd303a93b1b24e7601657c36d82247dceddf36e2e768c3e2e7e7cb,3,896 条目);
manifestPlugin-Version: 0.4.0、Plugin-Has-Config: true。 - 打包后验收:
node packaging/tools/verify-layout.mjs→ PASS(10 项检查全绿,
avatarAssetStatus: 200、settingsScripts: 1、consoleErrors: 0、fps 中位数 104)。 - 本地验收(宿主探针 + 真实 SPW 日志):
- 托盘右键菜单显示
Folia(位于「桌面歌词」之后),点击可开 / 关; - 两条透明快捷键在快捷键管理中显示,窗口关闭时切换会持久化,下次打开按该模式启动;
- 打开过渡动画在普通 / 透明 / 穿透三种模式下均可见,封面取色生效,配置页开关可关掉;
- 设置页在线预览不再停在「正在连接 Folia」,切换导入字体后预览实时跟随;
- 非透明模式下左上角
×与右上角齿轮对称,点击关闭窗口;透明模式下不显示。
- 托盘右键菜单显示
- 版本号:
spw-folia/build.gradle.kts的version与PluginVersion均为0.4.0;Kotlin 侧
FoliaPlugin.PLUGIN_VERSION同步为0.4.0。
安装 / 升级
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.0.spmod。 - 授予
class-transform、library-read与key-bindings(快捷键;漏授可在插件权限页补授后重启)。 - 重启 SPW,
更多 → Folia;最小化后右键托盘图标也能看到 Folia(点击开 / 关)。 - 透明快捷键在 SPW 的「设置 → 快捷键管理」里(
播放页透明背景:开 / 关、
播放页透明背景 + 鼠标穿透:开 / 关),需要时打开对应动作的「全局」开关。 - 过渡动画开关在
模组管理 → spw-folia → ⋯ → 配置 → 播放页 → 打开过渡动画(默认关闭)。
可直接覆盖升级 v0.3.1(协议与设置格式均未变;web bundle 换成了 Folia 0.7.22)。
已知限制
- 智能动感模式(smart beat)本身仍然不可用:注入页没有节拍分析模型,宿主也从不发
beat事件。
因此「花火」的随拍开花 / 推镜、sonnet的推镜、diorama的涟漪这些节拍驱动的效果保持静止;
花火其余效果(逐字色块、烟花开花、镜头升降旋转、副歌辉光)正常。 - 系统字体列表不持久化:每次启动重新枚举(最多 400 个家族),不写进设置树;导入的字体包存
IndexedDB,设置树里只记家族名 / 标签。 - 打开总时长受 WebView2 冷启动限制:约 1.5–2 秒,过渡动画固定 0.8 秒淡入,先遮住加载过程再露出
页面。 - 其余限制与 v0.3.x 相同:全屏跟随播放器所在显示器、色键透明(键色仍为
rgb(255,0,255))。
spw-folia v0.4.0 预览版 1 — 花火样式、随机样式池、自定义字体、托盘入口、透明快捷键、打开过渡动画
预览版(pre-release):这是 v0.4.0 的预览版,用于本地/真机验收与早期反馈。安装方式与正式版相同;遇到问题请在 issue 反馈。正式版会在预览反馈处理完成后发布。
spw-folia v0.4.0 — 花火样式、随机样式池、自定义字体、托盘入口、透明快捷键、打开过渡动画
v0.4.0 是一次内容 + 体验更新:web bundle 换成上游 Folia 0.7.22,带来第 15 种歌词样式
「花火」;同时补上随机歌词样式池、自定义字体(导入字体包 / 系统字体)、字体栏预览默认显示当前
歌词,以及最小化托盘图标的 Folia 入口;播放页透明背景也接进了 SPW 的快捷键管理,可以绑成自己的
快捷键。这一版还新增了从封面取色的打开过渡动画、非透明模式下的左上角退出按钮,并修掉了一批真机
验收中发现的问题(启动粉紫闪屏、预览框一直转圈、预览字体不跟随、托盘入口不显示 / 点了不关闭)。
issue #9 的启动成功提示噪音也在这一版移除;PR #10(多屏跟随、Alt 跨屏拖动、呼出快捷键可配置)
一并合入。
重点
- 上游 Folia 0.7.22:新歌词样式「花火」
- 随机歌词样式池 + 每首歌换一种 + 骰子重抽
- 自定义字体(导入字体包 / 系统字体),字体预览默认显示当前歌词,预览框字体实时跟随
- 托盘右键 Folia 入口(与「桌面歌词」同组,点击即开 / 关)
- 播放页透明背景快捷键两条(默认 Ctrl+Alt+G / Ctrl+Alt+Shift+G)
- 打开过渡动画:封面取色、透明→不透明淡入、默认关闭(配置页开关)
- 播放页左上角退出按钮(非透明模式)
- 修复:透明模式下打开时的粉紫闪屏、"正在连接 Folia"预览框、预览字体不跟随、托盘入口缺失
新增 / 变更
-
上游 Folia 0.7.22 适配 ——
poc/webview/dist/dist里的 web bundle 由 Folia 0.7.21 换成
0.7.22,取自Folia-Setup-0.7.22.exe,未做修改。 -
新歌词样式「花火」(hanabi) —— 第 15 种样式:日系歌词 PV 画风,歌词带色块逐字出场,烟花
沿字开花,镜头之间随升空烟花旋转上升,副歌亮起辉光;注册在 Folia 的 order 26,即绘光
(lumiere,25)与流光(classic,30)之间;设置页复用 Folia 自己的HanabiSettingsPanel,缩略图
poc/settings-ui/public/thumbs/hanabi.jpg。 -
随机歌词样式池 —— 「歌词样式」里可把 15 种样式的任意子集勾进随机池;打开「每首歌换一种」后
每首歌从池里抽一种且不连续重复;播放面板上的骰子按钮可当场重抽。随机池随设置快照保存。 -
自定义字体 —— 字体栏新增「自定义」磁贴,两条路都通:
- 导入字体包:
.ttf/.otf/.woff/.woff2,单个 ≤ 50 MB,存进 IndexedDB 并用
FontFace注册; - 系统字体:插件侧用 AWT 枚举字体家族,注入设置树的
systemFonts(最多 400 个家族)。
字重也一并恢复,选完字体可以继续调粗细。
- 导入字体包:
-
字体预览文本默认 = 当前播放歌词 —— 预览文本新增「当前歌词」选项并作为默认值,字体预览直接
渲染正在播放的那一行;空闲时回退到内置示例歌曲。随包的两首占位歌曲仍然可选。 -
字体预览跟随(修复) —— 设置页的「在线歌词预览」是嵌入的同源 iframe;此前导入字体只在播放器
主文档注册,iframe 文档里没有对应FontFace,所以预览始终回退系统字体。现在打开设置页时会把
已导入字体的字节注入预览 iframe 文档(在首次下发 config 之前完成,避免 Folia 渲染器不重排),
新导入字体也会再次注入并重发 config 触发重排;iframe 重新加载时自动重新注入。 -
托盘菜单 Folia 入口 —— SPW 最小化到托盘后,右键图标即可看到 Folia,位于「桌面歌词」之后、
同一分组内;点击是切换:窗口没开时打开,已经打开时关闭。
实现上除了 compose 层的菜单录制 hook,还增加了一层确定性原生补丁:hook
WindowsTrayMenuBuilderImpl.build()在返回的原生菜单项里强制插入 Folia 行,并在托盘管理器就绪后
主动刷新一次原生菜单 —— 这样不依赖 compose 的重录制时机。 -
播放页透明背景快捷键 —— SPW「快捷键管理」新增两条可重绑动作:
播放页透明背景:开 / 关(默认 Ctrl+Alt+G):切换透明背景;关闭时一并把「鼠标穿透」复位。播放页透明背景 + 鼠标穿透:开 / 关(默认 Ctrl+Alt+Shift+G):两者都关时一起打开,已经
全开时一起关闭。
窗口开着时按下立即生效:插件改设置树 → 宿主套色键 → 页面用自己的样式兼容表校验(
monet、
浅色主题下的lumiere等不支持透明的样式会拒绝并回退,此时提示「当前歌词样式不支持播放页透明
背景」);窗口没开时只写设置,下次打开直接以该模式启动。宿主原有的全局 Ctrl+Alt+B 逃生热键
保持不变;两条默认键位避开了宿主热键链(Ctrl+Alt+B / Y / P / Shift+B)和本机已被占用的
Ctrl+Alt+T。要让快捷键在 Folia 窗口聚焦时也生效,需要打开该动作的「全局」开关。 -
打开过渡动画(新) —— 每次打开 Folia 都播放,不区分普通 / 透明 / 鼠标穿透模式:
- 颜色从当前歌曲封面提取(HSV 聚类,最多 4 色;封面不可用时回退内置紫 / 粉 / 青 / 琥珀);
- 深色夜幕 + 极光光晕 + 漂浮火星 + 斜向扫光,居中
Folia字样,第二行「让歌词成为风景」使用
封面色的左→右渐变; - 动画从透明到不透明淡入,固定 0.8 秒;页面首帧就绪后淡出露出歌词页(整体时长受
WebView2 冷启动约 1.5–2 秒影响); - 多屏 / DPI 适配:按窗口所在显示器的物理边界摆放,并按该显示器 DPI(本机 180%)缩放火星半径、
文字粗细、细线等固定像素元素; - 配置页开关「打开过渡动画」,默认关闭;开关切换即时生效(下一次打开起)。
-
播放页左上角退出按钮 —— 未开启「播放页透明背景」时,播放页左上角显示圆形
×按钮(与右上角
设置齿轮严格对称、同样的玻璃质感与淡出行为),点击即关闭 Folia 窗口;透明模式自动隐藏。 -
PR #10:多屏跟随、Alt 跨屏拖动、呼出快捷键可配置 —— 已随本版合入(squash 为
b30df5d)。
修复
- 透明 / 穿透模式打开时的粉紫闪屏 —— 宿主不再使用 WinForms
TransparencyKey(它会重建窗口句柄并
闪一帧未上色键的洋红底色),改为直接用 Win32 分层窗口 API(WS_EX_LAYERED+
SetLayeredWindowAttributes(LWA_COLORKEY)),并在设置洋红背景色之前装好色键。 - 过渡动画只在部分模式可见 —— 遮罩窗口与播放器窗口同为 TopMost,播放器后显示会盖住遮罩;现在在
窗口创建 / 显示 / 激活时都把遮罩重新置顶,普通模式同样能看到完整动画。 - 「正在连接 Folia」预览框 —— 开发安装的 bundle 曾直接拷贝原始 dist,其中 PWA 的
sw.js会把
/preview.html导航拦截回index.html,导致预览 iframe 停在 OBS 连接占位页;现在本地安装也统一
经packaging/tools/stage-web.mjs生成preview.html+_host/preview-shim.js(新增 gradle
stageDevWeb任务),并清理了旧的 service worker 缓存。 - 修改字体后预览框不跟随 —— 见上文「字体预览跟随」。
- 托盘右键看不到 Folia —— 见上文「托盘菜单 Folia 入口」的原生补丁。
- 托盘点 Folia 无法关闭 —— 托盘条目改为切换开关。
- 动画时长设置无实际效果 —— 打开时长受 WebView2 冷启动主导,滑杆差异不可感知;按验收反馈删除
时长设置,动画固定 0.8 秒,配置页只保留开关。 - issue #9:启动不再弹成功提示 —— 只有失败才提示。
验证
- 构建:
cd poc/settings-ui && npx --no-install vite build→dist/settings-ui.js
2,241,477 字节。 - 构建:
cd spw-folia && JAVA_HOME="C:/Program Files/Java/jdk-21.0.10" ./gradlew plugin -q --offline
→build/libs/plugin-dev.spw.folia-0.4.0.spmod(205,707 字节,manifestPlugin-Version: 0.4.0、
Plugin-Has-Config: true、Plugin-Permissions: class-transform,library-read,key-bindings)。 - 宿主:
bash poc/webview/host/build.sh→spw-folia-host.exe151,552 字节(含新的分层窗口
透明实现与IntroCurtain.cs)。 - 本地验收(宿主探针 + 真实 SPW 日志):
- 托盘右键菜单显示
Folia(位于「桌面歌词」之后),点击可开 / 关; - 两条透明快捷键在快捷键管理中显示,窗口关闭时切换会持久化,下次打开按该模式启动;
- 打开过渡动画在普通 / 透明 / 穿透三种模式下均可见,封面取色生效,配置页开关可关掉;
- 设置页在线预览不再停在「正在连接 Folia」,切换导入字体后预览实时跟随;
- 非透明模式下左上角
×与右上角齿轮对称,点击关闭窗口;透明模式下不显示。
- 托盘右键菜单显示
- 版本号:
spw-folia/build.gradle.kts的version与PluginVersion均为0.4.0;Kotlin 侧
FoliaPlugin.PLUGIN_VERSION同步为0.4.0。
安装 / 升级
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.4.0.spmod。 - 授予
class-transform、library-read与key-bindings(快捷键;漏授可在插件权限页补授后重启)。 - 重启 SPW,
更多 → Folia;最小化后右键托盘图标也能看到 Folia(点击开 / 关)。 - 透明快捷键在 SPW 的「设置 → 快捷键管理」里(
播放页透明背景:开 / 关、
播放页透明背景 + 鼠标穿透:开 / 关),需要时打开对应动作的「全局」开关。 - 过渡动画开关在
模组管理 → spw-folia → ⋯ → 配置 → 播放页 → 打开过渡动画(默认关闭)。
可直接覆盖升级 v0.3.1(协议与设置格式均未变;web bundle 换成了 Folia 0.7.22)。
已知限制
- 智能动感模式(smart beat)本身仍然不可用:注入页没有节拍分析模型,宿主也从不发
beat事件。
因此「花火」的随拍开花 / 推镜、sonnet的推镜、diorama的涟漪这些节拍驱动的效果保持静止;
花火其余效果(逐字色块、烟花开花、镜头升降旋转、副歌辉光)正常。 - 系统字体列表不持久化:每次启动重新枚举(最多 400 个家族),不写进设置树;导入的字体包存
IndexedDB,设置树里只记家族名 / 标签。 - 打开总时长受 WebView2 冷启动限制:约 1.5–2 秒,过渡动画固定 0.8 秒淡入,先遮住加载过程再露出
页面。 - 其余限制与 v0.3.x 相同:全屏跟随播放器所在显示器、色键透明(键色仍为
rgb(255,0,255))。
spw-folia v0.3.1 — 兼容 SPW 1.19.0 以上(宿主类名运行时解析)
spw-folia v0.3.1 — 兼容 SPW 1.19.0 以上(宿主类名运行时解析)
v0.3.1 是兼容性更新,功能与 v0.3.0 完全一致:不再把宿主混淆类名写死在代码里,因此
SPW 1.19.1 / 1.19.2 等补丁版也能直接用,无需等插件再发一版来适配。
为什么需要这一版
SPW 每次发版都会重新跑 R8,插件要挂的类和方法每次都会被改名:
| 宿主编译 | 菜单文件门面 | MenuFlyoutScope | PopupState |
|---|---|---|---|
| 1.19.0(商店 / dev23) | androidx.compose.ui.ē |
androidx.compose.ui.ন |
androidx.compose.ui.Ⴈ |
| 1.19.2(Steam) | androidx.compose.ui.घ |
androidx.compose.ui.Ը |
androidx.compose.ui.ะ |
v0.3.0 之前这些名字是常量,并且 start() 里有一道 spwVersion != "1.19.0" 的硬门禁。
结果是 1.19.2 上插件照常加载、播放回调照常走,但日志只有一行
unsupported SPW version 1.19.2; hook not installed,更多菜单里不出现 Folia。
更新内容
- 宿主目标运行时解析(新增
HostTargets.kt)—— 分三层,逐层校验:host-targets.json缓存(按spwVersion键,写false不写失败结果);- 内置映射表(1.19.2 / 1.19.0);
- 结构扫描:读
java.class.path的宿主 jar,按
(Track, PiscesMediaItem, <PopupState>, Navigator, State, <MenuFlyoutScope>)这个唯一签名
认方法(其中 Track / PiscesMediaItem / Navigator / State 未被混淆),再用「接口 + 静态
(self, Function0, Function2)桥」认出 scope,剩下那个androidx.compose.ui.*就是
PopupState。
- 每层结果都用反射验证(方法签名、scope 桥、
dismiss()、调色板 getter)后才采用,
旧名字不会因为"看着像"被误用; - 未知版本在后台线程扫描(约 0.5–2 s,宿主启动不被卡住),结果写入缓存,之后每次启动
都走缓存; - 扫描按类文件里解析出来的真实
this_class认类——这个 jar 的 zip 条目名不可信
(androidx/compose/ui/Ϡ.class里装的是…/ϡ)。
- 版本门禁改为语义版本比较 ——
>= 1.19.0才注入;低于 1.19.0 仍明确拒绝并记日志
(unsupported SPW version x.y.z (need >= 1.19.0)),不再是"只认 1.19.0 这一个字符串"。 - 主题色同样改为解析 —— 菜单项文字/图标色仍取宿主调色板
ʰ.Ԩ(composer).Ԩ(),但访问类名不再硬编码;解析失败才回退到固定浅色。 - flyout 去重改为按字段类型发现 —— 不再依赖
MenuFlyoutScope实现里的字段名Ϳ,
改为找实现类里第一个List字段;找不到就退化为按 scope 实例去重。 - handshake 上报真实宿主版本 ——
hello行里的spwVersion以前恒为常量1.19.0,
现在上报pluginContext.spwVersion(1.19.2等)。
播放控制(FoliaPlaybackControls)未改动:PlaybackQueueState 的字段顺序与
com.xuncorp.spc.core.queue.Ϳ 在 1.19.2 上未变,playback controls ready 照常解析。
验证
- 真实 SPW 1.19.2(Steam 渠道,
-Djpackage.app-version=1.19.2):host menu profile:1.19.2: androidx.compose.ui.घ.Ϳ(Track, PiscesMediaItem, ะ, Navigator, State, Ը)hookMethod installed: … (profile:1.19.2);重启后走缓存host menu cache: …Folia item appended (scope=androidx.compose.ui.ʎ$Ϳ, bridge=Ϳ)—— 菜单项成功进 flyoutplayback controls ready: … controller=com.xuncorp.voxzen.service.PlaybackController …- 打开 Folia 全屏:
host fps120、state #100 -> helper连续、host-exit code=0干净退出 spw-folia.log无 hook 相关失败;唯一 error 是既有的
ServiceWorker registration … Failed to access storage(离线 bundle 无 SW,v0.3.0 相同)
- 真实 SPW 1.19.0:内置映射表即 v0.3.0 的已验证目标,回退路径不变。
- 构建:
gradle plugin(JDK 25 toolchain,Kotlin 2.4.20,workshop API0.1.0-dev23)
BUILD SUCCESSFUL,.spmod中classes/更新,web/native/licenses/与 v0.3.0 逐字节相同。
安装 / 升级
- 关闭 Salt Player for Windows。
- mod 管理器里删除旧版
spw-folia,导入spw-folia-0.3.1.spmod。 - 授予
class-transform与library-read。 - 重启 SPW,
更多 → Folia。
可直接覆盖升级 v0.3.0(协议、设置格式、web bundle 均未变)。
已知限制
- 未知的新宿主版本依赖结构扫描,首次启动会多花 0.5–2 s(后台进行,结果缓存)。若上游把
菜单构建方式整体重写(例如不再用PopupState+MenuFlyoutScope),扫描会失败并记
could not locate the 更多 flyout,需要重新适配。 - 其余限制与 v0.3.0 相同:色键透明(
rgb(255,0,255))、仅主屏全屏、monet与浅色主题下的
lumiere在透明下不可用、WebView2 进程偶发退出暂无自动重试。
spw-folia v0.3.0 — 开源链接 · 按钮样式统一 · 透明字体随系统明暗 · 底部播放控制条(可点可用 · 亚克力 · 闲置隐藏 · 侧键退出)
spw-folia v0.3.0 — 开源链接 · 按钮样式统一 · 透明字体随系统明暗 · 底部播放控制条(可点可用 · 亚克力 · 闲置隐藏 · 侧键退出)
v0.3.0 是 UI/元数据 + 播放控制通道的增量更新:插件管理补开源链接、右下角按钮去掉专属色环、
透明窗口字体颜色改随系统深浅模式、新增底部播放控制条;并在验收反馈后完成 播放控制接线
(上一版协议只读,按钮原本只能 disabled)与 HUD 观感修正(亚克力毛玻璃、鼠标闲置自动下滑
隐藏,避免遮挡歌词)。协议新增 control(page→host→plugin)与 queue(plugin→host→page)
两条向后兼容消息,旧页面/旧插件互不感知。本地验收反馈又完成一轮体验修复:
透明窗口下 HUD 悬停无法展开(改为 pointermove 坐标命中)、右下角透明面板删掉说明
文案、鼠标侧键「返回」可直接退出 Folia。
更新内容
- 插件管理「开源链接」可用 —
spmod{}块新增PluginOpenSourceUrl = "https://github.com/azkbzxf/spw-folia",
经 workshop gradle 插件写入 MANIFEST.MF 的Plugin-Open-Source-Url属性(字段名经
SpmodExtension 常量池与两个已装第三方模组的 MANIFEST 双重实证)。SPW 模组管理菜单里的
「开源链接」不再置灰,指向本仓库。「配置」仍置灰是预期(PluginHasConfig=false未变)。 - 右下角按钮样式与右上角齿轮统一 — 删除
.spw-chrome-btn--player的全部专属覆盖
(金色rgba(214,168,84,…)描边 + 2px 色环 + 深色底),视觉完全继承.spw-chrome-btn
基准样式(半透明黑底 + 白色细描边 + blur 12px),hover 效果与齿轮一致。 - 透明窗口字体颜色随系统深浅模式 — 开启「透明播放背景」后,文字明暗由 Windows 系统
应用模式(matchMedia('(prefers-color-scheme: dark)'),WebView2 跟随系统)决定,不再由
封面平均亮度决定;色相仍取自封面,主/次/弱化三色经ensureContrast锁到系统模式
一侧(浅色系统亮度压到 ≤0.45、深色系统抬到 ≥0.55),深色封面 accent 不会再糊在浅色系统
的浅字区。系统深↔浅切换实时重染;无封面时提供 Daylight Default / Midnight Default 双
fallback。非透明窗口的取色路径逐像素保持 v0.1.x 行为不变;lumiere 兼容检查改用
「有效明暗」(opaque=封面、transparent=系统)。 - 底部播放进度条 — 歌词舞台底部中央新增播放 HUD:收起态为小 pill(当前时间 / 细进度条
带位置圆点 / 总时长),悬停 150 ms 展开成控制条(白色播放圆钮、上一首、曲名、下一首、
随机、队列 + 进度行),移出 260 ms 收起。进度用 rAF 直绘 DOM,不触发 React 重渲染;
位置由state快照按tsMs + rate外推(漂移超 5 s 不外推)。鼠标穿透模式下整条不渲染
(零像素、零热区,点击照常落向桌面);设置 overlay 打开时同样让位。 - 播放控制条按钮全部可用 — 播放/暂停(含状态图标切换)、上一首、下一首、随机播放
(点亮态 + 记忆原播放模式,关闭随机恢复)、播放列表(面板 420px,当前曲高亮,点行切歌),
进度条支持鼠标拖动/点击 seek。page→host 新增{"type":"control",...},宿主转
{"t":"control",...}交给插件;插件经反射调用宿主内部
com.xuncorp.voxzen.service.PlaybackController(playOrPause/previous/next/seekTo/ changePlaybackMode/playMusicAt),队列经PlaybackQueueState回读后以{"t":"queue"}
推回页面(mode/random/index/items)。宿主版本经插件启动版本门锁到 SPW 1.19.0。 - HUD 亚克力毛玻璃 — 半透明底 +
backdrop-filter: blur(28px) saturate(1.7) brightness(1.08)- 顶部内高光 + 外阴影,透出舞台颜色;透明窗口(无法采样桌面)回退为致密亚克力底、不启用模糊。
- 鼠标闲置自动隐藏 — 2.6 s 无
pointermove/pointerdown/keydown时整条下滑出画面(不遮歌词),
任意鼠标移动/按键即滑回;播放列表面板打开或拖动 seek 时暂停倒计时;键盘 Tab 聚焦到 HUD 时
自动展开。悬停 150 ms 展开、移出 260 ms 收起的原交互不变。 - 透明窗口下播放条可正常悬停展开 — 透明模式的输入捕获窗转发鼠标消息时,Chromium 对每次
forwardedWM_MOUSEMOVE都会先发 pointerover/enter、紧接 pointerout/leave,原 pointerenter/
leave 悬停逻辑被来回抵消(只唤醒不展开)。改为窗口级pointermove坐标命中「稳定展开区」
(620×96、底边 28 px)判定悬停/移出,单次移动即可展开;亚克力、闲置隐藏、透明扁平样式不变。 - 透明面板去说明文案 — 右下角「播放页透明背景」面板删掉两行开关说明与底部说明段落,只保留
两个开关和兼容性警告,更干净;面板功能不变。 - 鼠标侧键「返回」退出 Folia — XBUTTON1 与 ESC 同义:设置 overlay 打开时先关 overlay,否则
退出 Folia 模式(reason=user,插件保持加载);XBUTTON2「前进」不退出。透明模式下由输入捕获窗
转发WM_XBUTTON*,两条渲染路径共用页面同一个 mousedown 处理器。
验证
- 构建 — 宿主
bash poc/webview/host/build.shOK(spw-folia-host.exe134,144 B,控制/队列转发 +
透明捕获窗侧键转发新代码);插件./gradlew plugin(JDK 25)BUILD SUCCESSFUL;设置岛pnpm build通过。 - 设置 UI 回归(puppeteer-core + 系统 Edge,dev.html 真实注入路径) —
research/settings-ui/v030-playback-controls-verify.mjs31/31 PASS:5 按钮全可用、点按发出
正确toggle/previous/next/shuffle/queue-play(index,random)/seek(positionMs)、播放列表面板 7 行/
当前行高亮/点行切歌、ESC 只关面板、拖动/点击 seek≈50%、亚克力(半透明 + blur + 内高光)与
闲置 2.6 s 隐藏/移动唤醒;research/settings-ui/v030-playback-verify.mjs10/10 PASS(收起/
展开/透明平铺/鼠标穿透渲染回归)。截图research/settings-ui/v030-controls-{bar,queue,idle-hidden}.png。 - 打包级 E2E —
node packaging/tools/verify-playback-controls.mjsPASS 15/15:拉起打包宿主,
注入 queue/state,经command eval点击 5 按钮 + seek,断言宿主 stdout 收到
toggle/previous/next/shuffle/queue-play(index=2,random=false)/seek(≈50%)、面板 ≥6 行、ESC 只关
面板、退出码 0、consoleErrors=0;截图packaging/out/packaged-controls-{bar,queue}.png。 - 打包布局验收 —
node packaging/tools/verify-layout.mjs:PASS,consoleErrors=0。 - 透明 HUD 验收 —
node packaging/tools/verify-transparent-hud.mjs6/6 PASS:面板仅剩 2 行
开关(0 hint / 0 note)、透明下data-flat、悬停展开 620×96、移开收起、闲置 3.2 s 隐藏、再移动
唤醒并展开;报告packaging/out/verify-transparent-hud-report.json。 - 鼠标侧键验收 —
node packaging/tools/verify-mouse-back.mjs4/4 PASS(真实 OS 鼠标事件):
不透明窗口侧键返回退出(reason=user、exit 0)、透明窗口侧键返回退出(catcher 转发)、侧键前进
不退出、设置 overlay 打开时第一次返回只关 overlay 第二次退出;报告
packaging/out/verify-mouse-back-report.json。 - 真实 SPW 1.19.0 验收 — 插件日志出现
playback controls ready: loader=… controller=…PlaybackController queueState=… modeClass=… itemClass=…PiscesMediaItem stateValue=StateFlow.getValue()与
queue -> helper (2931 bytes, force);全屏歌词实拍:展开态亚克力透出舞台色、闲置 3.4 s 后播放条
完全滑出画面(poc/hook/shots/v030-acrylic-expanded-0.png、v030-idle-hidden-0.png);透明背景
下悬停展开实拍poc/hook/shots/v030-final-trans-hover-0.png、v030-final-clean-0.png;面板无注释
文案poc/hook/shots/v030-pbg2-click-0.png;侧键返回日志folia window closed (reason=user)/
host-exit code=0(透明与不透明两条路径均验)。 - diff 审查 — 三方写入范围互不重叠:插件(FoliaPlaybackControls.kt 新增 + FoliaHostBridge/
FoliaPlugin)、宿主(HostForm.cs + Shim.js)、设置 UI(bridge.ts + PlaybackBar + 新增
PlaybackQueuePanel + app.css);协议文档与验收脚本由 Lead 统一维护。
产物
| 文件 | 大小 | sha256 |
|---|---|---|
spw-folia-0.3.0.spmod |
13,178,430 B(1943 entries) | 7f79d9dc8184c1119ce42a6dc59e1036fb3ffee6bb883c0c603cfdd977d80744 |
spw-folia-0.3.0.spmod.json |
488 B | 82d54753a922d582bc8331bdaead16bd28ccb69e0f892ddebcd49baf2aff94bf |
SHA256SUMS.txt |
88 B | 8390a0c2185ce29d7696339c2036dfeae2f72ec966935b0c6ce9e09e573015c8 |
包内构成:插件 classes plugin-dev.spw.folia-0.3.0.spmod(112,411 B,sha256
18e89d9eebfe5575e488d7a5fec4caf604e977c89fd5b77efe68b679037f8767,Gradle 产出的
classes/ + metadata 打包件,解压后 classes 33 个文件共 238,514 B;含播放控制反射桥/队列
provider/control 路由)+ native 辅助进程 spw-folia-host.exe 134,144 B(sha256
536037d5864217983dc34667e0fb1d94d8d79e1658318355dc70bd0e52c606af,新增 control→stdin 与
queue→page 转发、内嵌新版 Shim(侧键返回退出)与透明捕获窗 WM_XBUTTON* 转发)+ 完整 Folia
0.7.21 web bundle(未改动)+ 设置岛 settings-ui.js 1,093,739 B(sha256
62609003c09b43ed4cd05258af810fb21c334525d4d6b4ca7141c48140b011bd,控制条/播放列表/亚克力/
闲置隐藏/透明悬停修复/面板文案精简均在此)+ 许可证。SHA256SUMS.txt 随发布资产一并上传。
安装 / 升级
- 关闭 Salt Player for Windows。
- 打开 mod 管理器,删除旧版
spw-folia,导入spw-folia-0.3.0.spmod。 - 授予
class-transform与library-read两项权限。 - 重启 SPW,
更多 → Folia。
可直接从 v0.2.2 覆盖升级(协议、设置格式、web bundle 均未变)。系统依赖
(WebView2 Runtime / .NET Framework 4.8)见 runtime-requirements.md。
已知限制
- 播放控制依赖宿主内部类 — 反射目标为 SPW 1.19.0 的内部实现(
PlaybackController/
PlaybackQueueState等),插件已用spwVersion == 1.19.0版本门锁住;SPW 升级后若内部
改名,控制通道会安全降级(按钮仍可点、动作被忽略并记 warning 日志),不会崩溃。 - 其余限制与 v0.2.2 相同:色键透明仍为
rgb(255,0,255)方案;仅主屏全屏;monet与浅色主题下的
lumiere在透明下不可用;WebView2 浏览器进程偶发退出暂无自动重试。
spw-folia v0.2.2 — 鼠标穿透重构为系统级 shaped region(借鉴 SPW-island)
spw-folia v0.2.2 — 鼠标穿透重构为系统级 shaped region(借鉴 SPW-island)
v0.2.2 把 v0.1.9 引入的「鼠标穿透」从合成点击回放重写为操作系统级命中测试,思路借鉴开源
桌面挂件项目 SPW-island(Kotlin/Compose Desktop + JNA)的 shaped input peer 设计。协议与设置
格式均与 v0.2.1 相同,这是 v0.2.0 以来第一次动宿主输入路径的行为级重构。
更新内容
- 重构:鼠标穿透改为系统级 shaped region — 旧机制(v0.1.9)是「隐藏捕获窗 →
mouse_event
合成一次左键 → 220 ms 后恢复」的时序回放,缺陷成堆:只回放左键,右键/滚轮/悬停被整吞、
拖拽必断、极快连点可能丢一次,且合成输入会被提权窗口与反作弊软件拦截。新机制直接把
捕获窗口的窗口区域(window region)做成命中测试:页面上报的 hitbox 矩形并集(CSS → client
按ClientSize/hitViewport缩放,四周放大 ±4 CSS px 容差)经CreateRectRgn+CombineRgn(RGN_OR)
合成后SetWindowRgn。区域之外的输入在 OS 命中测试层直接落向下层窗口——点击、右键、滚轮、
悬停、拖拽全部真实穿透,零合成输入,提权窗口 / 反作弊场景同样可用。 - 拖拽连续性 — 热区内按下任一鼠标键即把区域临时扩展为整窗(避免拖出热区后事件断流),
全部按键抬起即恢复;若 button-up 被 WebView2 捕获吃掉,下一次移动用GetAsyncKeyState
自愈,区域不会永久保持整窗。 - fail-open 保守策略 — 穿透关闭、页面未上报矩形或 viewport 非法时区域置空(整窗可点):
宁可多点到页面,也不吞掉用户的点击。 - 保留 v0.1.10 崩溃修复 — 消息参数一律
unchecked((int)ToInt64())取值(x64 下
WParam.ToInt32()检查窄化会在滚轮向下 delta -120 时抛OverflowException崩掉 WndProc),
MakeLParam两半字掩码不变。 - 借鉴出处 — SPW-island
WindowsGpuInput.kt/WindowsOverlay.kt的
WS_EX_TRANSPARENT|WS_EX_LAYERED整窗穿透 + shaped 小窗反选区域双窗方案,被折叠成单一
「区域化捕获窗」,OS 命中测试语义等价、窗口数减半。设计细节见
live-transparency.md §4.7。
验证
- 旧机制零残留 — 全源 grep
mouse_event|catcherRestoreAt|IsInteractiveAt|FallThroughToDesktop| ShouldForward|MouseEventLeft无命中;Forward改为无条件转发到达的消息(能到达 = 在区域内)。 - 编译 —
bash poc/webview/host/build.sh(csc.exe,.NET Framework 4.8,x64)与
./gradlew plugin(JDK 25)均成功;版本号两处一致(build.gradle.ktsversion/PluginVersion
均为0.2.2)。 - 打包布局验收 —
node packaging/tools/verify-layout.mjs:PASS,10 项检查全过
(exitedZero/closedUser/bridgePresent/settingsInjected/settingsAutoOpened/
closeSettingsCommand/openSettingsCommand/escClosedSettingsOnly/visualizerCanvases/
noConsoleErrors),consoleErrors=0,fps 中位 107,spawnToReady 590 ms。 - 真机验收 — 安装进真实 workshop 后 owner 实测穿透行为(热区可点、其余位置点击/右键/滚轮/
悬停/拖拽直落下层),于 2026-10-21 验收通过。
产物
| 文件 | 大小 | sha256 |
|---|---|---|
spw-folia-0.2.2.spmod |
13,153,013 B(1941 entries) | ca74c7fc4b6ce807af582f8dcbae041f4001bbf8b291e86e3be29a30f9fa38ac |
spw-folia-0.2.2.spmod.json |
488 B | 419bf64bfd5a922f3e9cb5baeb68cd48aaaf01b5af29950068aae8411d4511dc |
SHA256SUMS.txt |
88 B | e3d4f96bb69264c53b34668d90300aaada972c00415a5b3390ce772d06ad00eb |
包内构成:插件 classes plugin-dev.spw.folia-0.2.2.spmod(94,303 B,sha256
4893ce145c10ac91a8bfffde4aed293b113baf27a2b8d7cc655a81484ea4d7cb)+ native 辅助进程
spw-folia-host.exe 131,584 B(sha256 e4248044c5bfad60aab7145ce37c3af9ba82e62413e4ca34b92d9c403b9d1ee9,
本次重构宿主输入路径)+ 完整 Folia 0.7.21 web bundle 与设置岛(settings-ui.js 1,071,968 B,
sha256 eeb8a128359531fa3f1d68d6ce6db5e808c3128f92c5f933e040ae7bae95ad34,未改动)+ 许可证。
安装 / 升级
- 关闭 Salt Player for Windows。
- 打开 mod 管理器,删除旧版
spw-folia,导入spw-folia-0.2.2.spmod。 - 授予
class-transform与library-read两项权限。 - 重启 SPW,
更多 → Folia。
可直接从 v0.2.1 覆盖升级(协议、设置格式、web bundle 均未变)。系统依赖
(WebView2 Runtime / .NET Framework 4.8)见 runtime-requirements.md。
已知限制
- 穿透热区来自页面上报的 hitbox 矩形(±4 CSS px 容差);上报缺失或解析失败时退化为整窗
可点(fail-open),按住拖拽期间热区临时扩展为整窗、松开恢复。 - 其余限制与 v0.2.1 相同:色键透明仍为
rgb(255,0,255)方案;仅主屏全屏;monet与浅色主题下的
lumiere在透明下不可用;WebView2 浏览器进程偶发退出暂无自动重试。