Repository navigation
Conversation
[Medium impact] The changes affect desktop lyrics across the app's native, main-process, and renderer paths. 应先修复生命周期切换与跨屏替换之间的竞争,再合并;超大缩放和邻屏选择问题为非阻断问题。 FindingsFix with agent prompt### Issue 1
src/main/windows/desktop-lyrics.ts:622-640
跳屏等待页面加载期间,用户隐藏歌词不会取消替换。此时 `desktop_lyrics` 的名称仍指向旧窗口,所以隐藏操作只隐藏旧窗口。页面随后就绪,`showAndWaitForPage()` 仍会显示新窗口,并移交名称、销毁旧窗口,导致刚隐藏的歌词重新出现。
等待期间修改 `window.lifecycle`,也会被失败分支的 `window = previous` 覆盖。请让隐藏和生命周期切换取消当前跳屏,并在显示、移交或恢复窗口前确认这次替换仍有效。
### Issue 2
modules/window/src/linux/wayland/handlers/grab.rs:285-287
`resolve()` 在窗口超过屏幕大小后只限制位置,没有限制宽高,而此时 `keep_inside()` 会原样返回位置。例如,1280 像素宽的屏幕上,宽度 800、左边距 40 的窗口向左扩展 600 像素,会实际发送宽度 1400、左边距 -560 的请求,让部分歌词越过屏幕边界。跨过尺寸上限时位置还会突变:扩展 480 像素时左边距为 0,再扩展一像素就变成 -441。
页面上报屏幕尺寸后虽会纠正,但下一次移动仍按手势起点重算,再次覆盖纠正结果。这是非阻断的缩放问题。请在生成请求前限制宽高,并据此计算位置,不要依赖页面事后拉回。
### Issue 3
src/main/windows/desktop-lyrics.ts:364-367
光标位置仍停在原屏时,`displayBeyond` 只比较推动方向上的距离,没有检查另一方向是否相交。原屏右上方有一块屏幕、右侧还有真正相邻的屏幕时,两者都可能得到零距离,最终按显示器枚举顺序选中右上方那块。执行完整跳屏后,错误目标还被用于替换窗口和保存输出;只有右上方屏幕时也会发生同样的跳转。
这是非阻断的多屏选屏问题。请先检查屏幕在另一方向上的重叠范围,再选择用户推向的邻屏,避免用户需要再次移动歌词来纠正位置。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Summary桌面歌词在默认支持的 Wayland 桌面上改用 overlay 层,并由原生代码驱动拖动和缩放。
Reviews (8) · Last reviewed commit: "feat(lyrics): 桌面歌词在 Wayland 上改用 layer sh..." · Reviewed by Greptile |
| .filter((display) => display.id !== from.id) | ||
| .map((display) => ({ display, distance: startsAt(display) })) | ||
| .filter(({ distance }) => distance !== null && distance >= 0) | ||
| .sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))[0]?.display ?? null |
There was a problem hiding this comment.
光标位置仍停在原屏时,displayBeyond 只比较推动方向上的距离,没有检查另一方向是否相交。原屏右上方有一块屏幕、右侧还有真正相邻的屏幕时,两者都可能得到零距离,最终按显示器枚举顺序选中右上方那块。执行完整跳屏后,错误目标还被用于替换窗口和保存输出;只有右上方屏幕时也会发生同样的跳转。
这是非阻断的多屏选屏问题。请先检查屏幕在另一方向上的重叠范围,再选择用户推向的邻屏,避免用户需要再次移动歌词来纠正位置。
Artifacts
- Loads each revision's complete module from Git and drives its registered push handler across nine mocked display layouts, making the reproduction rerunnable.
- Runs the nine layouts against the base revision and captures no registered push handler or window replacement, establishing the pre-change behavior.
- Runs the same layouts against PR312 head and captures selected outputs, settings writes, replacement lifecycle, and call stacks, confirming the diagonal-target defect.
- Captures both complete source revisions using Git and checks the scoped file for local differences, preserving the executed implementation and confirming no source fix was applied.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/windows/desktop-lyrics.ts
Line: 364-367
Comment:
**歌词跳到错误邻屏**
光标位置仍停在原屏时,`displayBeyond` 只比较推动方向上的距离,没有检查另一方向是否相交。原屏右上方有一块屏幕、右侧还有真正相邻的屏幕时,两者都可能得到零距离,最终按显示器枚举顺序选中右上方那块。执行完整跳屏后,错误目标还被用于替换窗口和保存输出;只有右上方屏幕时也会发生同样的跳转。
这是非阻断的多屏选屏问题。请先检查屏幕在另一方向上的重叠范围,再选择用户推向的邻屏,避免用户需要再次移动歌词来纠正位置。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.c6ce052 to
eafa0de
Compare
| await Promise.race([ | ||
| showAndWaitForPage(next), | ||
| new Promise((_, reject) => { | ||
| setTimeout( | ||
| () => reject(new Error("the window built again never came up")), | ||
| LYRICS_HOP_TIMEOUT_MS | ||
| ); | ||
| }), | ||
| ]); | ||
| // Once it is up: the name is how everything else reaches this window, and | ||
| // the wrapper it is being taken from has to stop answering to it — the | ||
| // lookup takes whichever comes first, and the window behind that one is on | ||
| // its way out. | ||
| previous?.moveNameTo(next); |
There was a problem hiding this comment.
跳屏等待页面加载期间,用户隐藏歌词不会取消替换。此时 desktop_lyrics 的名称仍指向旧窗口,所以隐藏操作只隐藏旧窗口。页面随后就绪,showAndWaitForPage() 仍会显示新窗口,并移交名称、销毁旧窗口,导致刚隐藏的歌词重新出现。
等待期间修改 window.lifecycle,也会被失败分支的 window = previous 覆盖。请让隐藏和生命周期切换取消当前跳屏,并在显示、移交或恢复窗口前确认这次替换仍有效。
Knowledge Base Used: Desktop windows and overlays
Artifacts
- The executed Vitest harness drives real main-process modules with mocked Electron and checks both requested interleavings, making the reproduction inspectable.
- The executed generator adapts the existing window test mocks and authors the focused test, documenting exactly how the harness was constructed.
- The executed runner loads the three main modules from each specified revision, captures commands and exit codes, and restores production files afterward, making the comparison reproducible.
- Captured git commands print both revisions of the three exercised modules and numbered candidate lines, tying the runtime comparison to concrete source.
- One base Vitest run captures both control flows with hideHonored and lifecyclePreserved true, showing that neither newer action is overwritten.
- One head Vitest run captures lyrics becoming visible after hide and the old lifecycle wrapper replacing the new one after readiness failure, confirming the candidate.
- Captured git diff and HEAD checks show production files restored unchanged at the supplied head, confirming that validation left no production fix.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/windows/desktop-lyrics.ts
Line: 572-585
Comment:
**隐藏后歌词重新出现**
跳屏等待页面加载期间,用户隐藏歌词不会取消替换。此时 `desktop_lyrics` 的名称仍指向旧窗口,所以隐藏操作只隐藏旧窗口。页面随后就绪,`showAndWaitForPage()` 仍会显示新窗口,并移交名称、销毁旧窗口,导致刚隐藏的歌词重新出现。
等待期间修改 `window.lifecycle`,也会被失败分支的 `window = previous` 覆盖。请让隐藏和生命周期切换取消当前跳屏,并在显示、移交或恢复窗口前确认这次替换仍有效。
**Knowledge Base Used:** [Desktop windows and overlays](https://app.greptile.com/rainplus/-/custom-context/knowledge-base/yucling/open-orpheus/-/docs/desktop-windows-and-overlays.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.eafa0de to
619d225
Compare
|
似乎没有看到对大小调整做支持?iirc Layer Shell窗口也不支持WM处理的大小调整 |
|
有支持,大小调整是在 native proxy 里重放的,不是交给 WM 处理,所以在
因此 layer-shell 路径下既支持从窗口边缘拖动调整大小,也支持代码主动调整大小。这里的 |
|
适配了呀,移动到边缘有调整
|
619d225 to
f81052e
Compare
|
三轮评审的意见都处理了,逐条说一下改法。除了第 3 轮那条关于放弃窗口名字的(那个改动直接来自这次评审),其余每一条都补了对应的测试。 第一轮
第二轮竖屏按旋转方向: 跳屏期间隐藏 / 切生命周期:页面加载完之后、接管之前会再确认一次——开始跳屏时旧窗口在屏上,而现在已经不在了,就当作废(用比较而不是直接看
第三轮
另外:已 rebase 到 #311 之后#311 把 layer 与 popup 的 policy 合并成 验证: CI 那三个 workflow 目前还是 |
f81052e to
23c598f
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #312 +/- ##
==========================================
+ Coverage 57.39% 59.31% +1.92%
==========================================
Files 173 175 +2
Lines 19176 21252 +2076
Branches 1793 1882 +89
==========================================
+ Hits 11006 12606 +1600
- Misses 7320 7743 +423
- Partials 850 903 +53
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
很感谢你能尝试这个方案,但目前在KDE上的体验不尽如人意。 _20261015_095543.webm经测试与屏幕的缩放率似乎没有关系。此外,跨屏的体验上也不怎么好。 目前来看该方案还无法媲美手动通过窗口规则对其进行调整的体验,只是省略了用户初次使用手动配置的步骤并提供了位置记录,我认为暂时还不应该替换原有方案. |
|
我觉得要不仅在 wlroots 窗口管理器上启用好了 |
|
跨屏的体验确实是一个限制,不过这似乎挺难搞好 |
db79d3d to
3a71818
Compare
|
关于 KDE:谢谢实测。录屏我这边打不开(三次都是 422,可能是我的网络),所以下面是从机制推的,跟实际不符的话请纠正我。 现象:从描述和 niri 上的对照看,是歌词窗口追不上指针,而不是整个桌面被拖卡。这两件事成因完全不同——前者可以排除「每个 motion 事件都触发一次重排,把合成器灌爆」那条路(那条我能限流,但看现象不是它)。 判断:layer surface 的位置就是它的 margin,拖动是应用替合成器重放的,所以要求合成器在收到新 margin 的那一刻就重排这个 surface。KWin 不这么做,窗口就永远差一截,且与缩放率无关。协议里没有任何事件会告诉我们 layer surface 在哪、什么时候被重排过(这正是这套机制必须自己驱动手势的原因),所以这个能力测不出来,只能按合成器判。 改动:默认只在拖得动的合成器上开 overlay —— niri(这套东西长出来的地方)、Hyprland、wlroots 系(sway、river、labwc、wayfire:这一族在 margin 变化时同步重排,是共享实现,所以算「有理由」而不是「猜」)。其余(KDE、GNOME……)照旧用普通窗口,和这个 PR 之前完全一致:位置仍由合成器摆放(Wayland 上不再由应用请求坐标),窗口规则那套照旧生效。
想请你帮的忙:
还没处理的:greptile 报的触屏拖动确实存在——原生层接了 |
|
在 KDE 上问题依旧存在 可以将代码再次 rebase 到 main 吗?以便我能在 Sway 和 Hyprland 上测试 |
3a71818 to
89ce7cf
Compare
|
已经 rebase 到 关于 KDE,我想先确认一件事,因为两种情况的结论完全相反:你这次复测是带了
默认给 KDE 的是这样:窗口不做任何转换( 想确认当时走的是哪条路,可以搜日志:普通窗口那条会打
另外提前说清一件事,免得又有落差:位置记录只在 layer surface 那条路上有。普通窗口路径在 Wayland 上不记位置,因为客户端根本不知道自己的窗口在哪( Sway / Hyprland 就麻烦你了。这两个在名单里,其中 Hyprland 是我按「wlroots 系会在 margin 变化时重排」推的,没有实测过;Sway 同理。拖动跟手就是对的;不跟手的话请把它们也从名单里去掉 —— 名单里只应该放实测能用的, |
|
以下WM测试后存在问题 KDE _20261017_095953.webmHyprland _20261017_094519.webmlabwc _20261017_101656.webmWayfire _20261017_121438.webm其他白名单WM均正常 |
|
kde 测试的时候,是不是带了 LYRICS_LAYER_SHELL=1 kde 是不在白名单内的,默认应该不会启用 |
按要求启用了测试 |
桌面歌词一直是一个 xdg_toplevel,靠跳过任务栏、置顶和用户自己写窗口规则来假装自己是桌面歌词:层级盖不过全屏窗口,切工作区就消失,每个合成器的语法还各不相同,位置和尺寸也完全由 WM 说了算。zwlr_layer_shell_v1 的 overlay 层正是为这类东西准备的——层级由合成器保证,窗口不再是一个窗口,没有 WM 会去平铺或移动它,因此不需要任何窗口规则。 layer shell 唯独没有拖动和缩放:layer surface 的位置和尺寸属于应用,协议里只有 set_margin / set_size。这部分在原生层自己重放,见 modules/window/README.md 的「Moving and resizing a converted window」。 - modules/window: 新增 handlers/grab.rs,把客户端仍然会发的 xdg_toplevel.move / resize 接住,跟着指针位移改写 margin / size。位移始终从按下的那一刻算起——按住期间合成器给客户端的坐标一直是按住时那套几何下的,所以指针在窗口里的位置就是它当初抓住窗口的地方加上此后走过的全部距离;把「上一步」再累加起来会把一次拖动放大成事件个数倍 - modules/window: 手势认下启动它的那个键:别的键中途按下再松开不是拖动结束,否则窗口会停在原地不再跟手,贴着屏幕边时还会提前报一次推挤 - modules/window: 窗口整个留在屏幕内。layer surface 换不了输出,拖到边缘只能停住,停住之后光标继续走就会离开窗口,那一刻松手就把光标推过的那条边报给应用。留整块而不是留一条边,是因为接受鼠标的区域只有文字本身,露在屏外的那截永远抓不到,而且合成器会把「锚在左上」的 surface 缩到屏幕剩下的宽度 - modules/window: 屏幕尺寸依次从客户端的 zxdg_output_v1、该输出的 wl_output mode/scale、应用自己上报的尺寸里取,并按 wl_output 的 transform 判断屏幕是不是竖着用的;落在屏外的窗口会被拉回来,比屏幕还大的会被缩到屏幕大小,拖动则被夹在屏幕内。wl_output.mode 的参数是 flags 在前,这里原来读错了位,没有 zxdg_output_v1 时会把 1920×1080 记成 1×1920 并把窗口缩成一条 - modules/window: 新增 LayerShellOptions::output 与 onLayerSurfaceOutput,报出(以及按名字指定)convert 后的窗口落在哪块输出上;layer surface 的输出在创建时就定死了,这是唯一能把窗口重建到另一块屏幕上的办法 - modules/window: 直写合成器的 layer 请求改为在同一个临界区里解析窗口并整组写出,取锁顺序与代理自己的 outbound 路径一致(先写锁、再连接状态)。客户端随时可能销毁那个对象,而一个指向已经被销毁对象的请求是会让整条 display 连接断掉的协议错误 - main: overlay 只在拖得动的合成器上默认打开(niri、Hyprland、wlroots 系)。拖动是应用替合成器重放的,合成器得在收到新 margin 的那一刻重排 layer surface;KWin 不这么做,整个拖动过程窗口都追不上指针(与缩放率无关),其他合成器没有验证过,所以它们照旧用普通窗口,位置也照旧交给合成器,`LYRICS_LAYER_SHELL=1/0` 可以强制打开或关闭。判定是纯函数,见 src/main/windows/desktop-lyrics-layer.ts - main: 桌面歌词窗口注册 layer-shell 策略(overlay 层、左上锚点、margin 携带位置、exclusiveZone -1、键盘按需交互),并把拖动、缩放、横竖切换后的位置和所在的输出记进设置,下次启动按同一块屏幕的角还原;整块窗口放不下就回默认位置。横竖切换由渲染层给出目标方向,窗口已经是要的样子就不动它——窗口是按记住的尺寸创建的,每次挂载都取反会把刚创建好的尺寸翻回去 - main: 拖过屏幕交界线时把歌词窗口销毁重建到光标所在的那块屏幕上,贴着推过去的那条边,并把落点写进设置(新窗口已经在屏内时原生层不会报几何,只能这里写)。同一时间只跑一次跳屏,只有当前这个歌词窗口的推挤才算数;新窗口的页面起来之前不动旧窗口,页面加载期间用户把歌词藏起来或切了生命周期,这次跳屏就作废并清掉被放弃窗口的名字,失败或超时则把位置、输出和屏幕记录一起放回去 - gui: 渲染进程上报自己屏幕的尺寸(layer surface 只能由应用告诉原生层屏幕有多大);新增 paintPulse,指针在窗口上期间每帧重绘一个看不见的色块——layer surface 的几何是双缓冲的,客户端只在有东西要画时才 commit,空闲的窗口跟不上拖动 - docs: WM_RULES 按走不走 overlay 分开写,说明默认打开的是哪几个合成器、为什么以及怎么覆盖,并补上跨屏行为;模块 README 补上拖动模型、尺寸来源、整窗在屏内的理由、锁序以及生成绑定的构建说明 - 测试:Rust 230 项、JS 760 项。新增拖动位移不被事件个数放大的回归、推挤上报、整窗在屏内、输出尺寸来源、mode 事件解析(含旋转)、别的键不打断拖动、合成器判定、layer 请求与状态在同一临界区等用例 不支持 layer shell 的合成器(GNOME、KDE)默认一切照旧,走原来的窗口规则。
89ce7cf to
c50ac85
Compare
|
先说 KDE:它不在名单里,默认也不会走 layer surface。我把代码路径又确认了一遍: 能在这条路上出现的唯一入口是环境变量
日志在应用数据目录的 然后是重要的部分:你的实测把「wlroots 系」这个假设证伪了。原来的名单是「niri + wlroots 系」,理由是「这一族在 margin 变化时会同步重排」——现在看这个推理是错的,同一族里 sway、river 可以,labwc、wayfire 不行。所以名单按实测收窄成 niri、sway、river,Hyprland、labwc、Wayfire 都移出去了(KDE 本来就不在), 想请你确认三件事:
如果第 3 条是「没带」而 KDE 还是抖,除了日志还有个可能: 机制上这几种现象是同一件事的三个程度,都不是「支不支持 layer shell」:layer surface 的位置就是 margin,拖动是应用替合成器重放的,所以合成器必须在承载新 margin 的那次 commit 上就把 surface 摆过去。Hyprland 慢半拍(追不上指针),labwc、Wayfire 时快时慢(抖),KWin 干脆不动(永远追不上)。协议里没有任何 event 会告诉我们 surface 被摆到了哪里、什么时候摆的,应用侧没法自适应,只能按名字列。 如果你觉得「默认在三个上开」还是太激进,那就再退一步:默认全关,只留给 |
|
要是新的 commit 还是不行的话,我要不改成默认只开测试通过的,别的都默认关吧(包括 kde) |
|
或者说这个环境变量的开关或许可以移动到设置界面里,这样用户可以更方便的调整 |
| if let Some((width, height)) = bounds { | ||
| geometry.margin_left = keep_inside(geometry.margin_left, geometry.width, width); | ||
| geometry.margin_top = keep_inside(geometry.margin_top, geometry.height, height); |
There was a problem hiding this comment.
resolve() 在窗口超过屏幕大小后只限制位置,没有限制宽高,而此时 keep_inside() 会原样返回位置。例如,1280 像素宽的屏幕上,宽度 800、左边距 40 的窗口向左扩展 600 像素,会实际发送宽度 1400、左边距 -560 的请求,让部分歌词越过屏幕边界。跨过尺寸上限时位置还会突变:扩展 480 像素时左边距为 0,再扩展一像素就变成 -441。
页面上报屏幕尺寸后虽会纠正,但下一次移动仍按手势起点重算,再次覆盖纠正结果。这是非阻断的缩放问题。请在生成请求前限制宽高,并据此计算位置,不要依赖页面事后拉回。
Artifacts
Runner for base and head native resize validation
- Extracts the supplied revisions, temporarily includes the native test, and captures actual Cargo execution output; the comparison is reproducible without changing tracked product sources.
- Defines the generated pointer gesture, socket capture, bounds reports, and assertions used to exercise production code; the resize algorithm itself is not mocked.
Exact native test source executed against the PR base
- Contains the exact test included in the base build, with only head-specific APIs omitted; the gesture inputs match the head run.
Native resize execution before the PR
- Captures the compiled base run and socket observations for the generated gesture; the resize is suppressed and geometry remains unchanged.
Exact native test source executed against the PR head
- Contains the exact test included in the head build, including correction and release assertions; the candidate path is exercised through production protocol filtering.
Native resize execution with output overflow and correction reversal
- Captures actual server-bound requests, geometry callbacks, and policy values on head; width 1400 at left −560 returns after a bounds correction.
Current source locations and validation environment
- Captures numbered production source, repository status, and display/compositor availability checks; the cited lines are current and tracked product sources remain unchanged.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: modules/window/src/linux/wayland/handlers/grab.rs
Line: 285-287
Comment:
**缩放把歌词移出屏幕**
`resolve()` 在窗口超过屏幕大小后只限制位置,没有限制宽高,而此时 `keep_inside()` 会原样返回位置。例如,1280 像素宽的屏幕上,宽度 800、左边距 40 的窗口向左扩展 600 像素,会实际发送宽度 1400、左边距 -560 的请求,让部分歌词越过屏幕边界。跨过尺寸上限时位置还会突变:扩展 480 像素时左边距为 0,再扩展一像素就变成 -441。
页面上报屏幕尺寸后虽会纠正,但下一次移动仍按手势起点重算,再次覆盖纠正结果。这是非阻断的缩放问题。请在生成请求前限制宽高,并据此计算位置,不要依赖页面事后拉回。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
我看了一下,这个PR的体量有些大,我可能需要些时间才能审核完 |
|
辛苦了 |
问题
桌面歌词一直是一个
xdg_toplevel,靠「跳过任务栏 + 置顶 + 用户自己写窗口规则」来假装自己是桌面歌词。窗口规则做不到的部分:层级盖不过全屏窗口、切工作区就消失、每个合成器的语法还各不相同;位置和尺寸也完全由 WM 说了算。改动
zwlr_layer_shell_v1(wlroots 系、KWin、niri 等都已实现)的 overlay 层就是为这类东西准备的:层级由合成器保证(全屏窗口也盖不住),窗口不再是一个窗口,没有 WM 会去平铺或移动它,因此不需要任何窗口规则。layer shell 唯独没有拖动和缩放——layer surface 的位置和尺寸属于应用,协议里只有
set_margin/set_size,没有「用户把它拖走了」这件事。这部分在原生层自己做:modules/window)。转换后的窗口照样会发xdg_toplevel.move/resize,原生层把它接住,跟着指针位移改 margin / size。要害是位移始终从按下的那一刻算起:按住期间合成器给客户端的坐标一直是按住时那套几何下的,所以指针在窗口里的位置就是它当初抓住窗口的地方加上它此后走过的全部距离。把「上一步」再累加上去,一次拖动会被放大成事件个数的倍数(一批 8 个事件跑出 8 倍距离),所以每个事件都从手势起点重算。zxdg_output_v1的logical_size,退而求其次wl_output的mode/scale),也可能只能由渲染进程上报;三者依次兜底,并且窗口落在屏外时一定会被拉回来。应用侧(
src/main/windows/desktop-lyrics.ts)只负责三件事:要不要用 layer shell、跳屏、记住位置。哪些合成器默认启用:拖动能不能跟手,取决于合成器会不会在承载新 margin 的那次 commit 上就把 surface 摆过去——这是重排时机的问题,协议里没有任何回报可用于判断,所以只能按实测列。实测结果是
niri、sway、river跟手;Hyprland 追不上指针,labwc、Wayfire 拖动时会抖,KWin 根本不重排。因此默认只在这三个上启用,其余(KDE、GNOME、Hyprland、labwc、Wayfire……)照旧走普通窗口 + 窗口规则;想试可以设LYRICS_LAYER_SHELL=1,想明确关掉设0。判定是纯函数src/main/windows/desktop-lyrics-layer.ts,带单测。日志里会写明每个窗口最终是 layer surface 还是普通窗口,以及不在名单里的原因。需要注意
gui/src/lib/paintPulse.ts不是可有可无的:layer surface 的几何是双缓冲的,只有客户端 commit 才被采纳,而客户端只在有东西要画时才 commit,所以空闲的歌词窗口跟不上拖动。指针在窗口上期间每帧重绘一个看不见的色块,是这里唯一确定有效的办法(modules/window/README.md里写了这条对应用侧的契约)。LYRICS_LAYER_SHELL=0时一切照旧,走原来的窗口规则;docs/WM_RULES.md已按两种情况分开写。pnpm build:modules。没构建的话应用会打一条日志并让歌词退回普通窗口,而不是变成一个拖不动的哑巴窗口(src/main/window.ts的isLayerShellNativeComplete)。测试
cargo test -p window241 项、pnpm test761 项通过;cargo clippy --workspace --all-targets --all-features -- -D warnings、cargo fmt --all --check、pnpm lint、pnpm format:check干净。已在 niri(双屏,副屏在主屏左侧)上手工验证拖动、缩放、横竖切换、跨屏跳转与重启还原。其余合成器的实测结果见上,名单会随反馈调整。