From f72528c474ac37e6e8ca8be7ecdc44606a38100a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 15 Oct 2026 19:56:48 -0400 Subject: [PATCH] R6.12 round 2 fix: unbundled workers ask for accessory Co-Authored-By: Claude Opus 5.5 (1M context) --- agents.md | 4 ++-- crates/ely-lok/src/ffi/accessory.rs | 15 +++++++++++++-- tasks/reviews/R6.12-r2.md | 5 +++++ tests/scripts/dock-macos.sh | 14 ++++++++++---- 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 tasks/reviews/R6.12-r2.md diff --git a/agents.md b/agents.md index 296a90c..7213c1e 100644 --- a/agents.md +++ b/agents.md @@ -170,7 +170,7 @@ tests/scripts/pdf-recovery.sh 真窗口 ElyPDF:加锁文件的封存恢复点 tests/scripts/autosave.sh 真窗口 ElyDoc:自动保存开关、停手即写、外部改动不覆盖(需辅助功能权限) tests/scripts/print.sh 真窗口 ElyDoc:打印用文档自己的纸张,取消不动文档(需辅助功能权限) tests/scripts/show-fullscreen.sh 真窗口 ElyShow:F5 放映的窗口盖住整块屏幕(菜单栏与程序坞之上,需辅助功能权限) -tests/scripts/dock-macos.sh dist 里 ElyDoc、ElyTable、ElyShow 各启动数次:LOK worker 从未登记为程序坞应用,图块照出 +tests/scripts/dock-macos.sh dist 里 ElyDoc、ElyTable、ElyShow 与包外的 target/debug/ely-doc 各启动数次:LOK worker 从未登记为程序坞应用,图块照出 tests/scripts/windows-installer.sh 假应用的安装程序:所选目录原有的文件经两次安装与卸载都在,旧版按注册表清理,文件被占用或程序在运行时什么也不改,写不进时放回旧版(缺 makensis 时 SKIP) tests/scripts/types.sh packaging/types.tsv 的文件类型在 Linux 桌面项、Windows 安装程序与 macOS 文档类型里一致,从不设为默认 tests/scripts/layout.sh 真窗口:六个应用在 640、800、1100 宽下无元素越出窗口左右,留截图(需辅助功能与屏幕录制权限) @@ -304,5 +304,5 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely - 2026-10-15(用户报:ElyMark 代码块后的“你好↵测试”离开输入框后成了“你好 测试”):单个换行照 Typora 的默认(编辑时保留,导出与打印不保留):组件库 93efa7f 起 `MarkdownReader::new`(屏上:可视、预览、右键与命令的读法)把软换行读作换行,`MarkdownReader::joined`(输出)照 GFM 读作空格;标题的大纲与目录文字照旧一行。ElyMark 的 HTML、PDF、富文本复制与纯文本导出用 `joined`(R6.7)。组件库 c31d092 与 26af4be:画出成一个换行的 CRLF 对回其起点(原先按在 CRLF 行末键入落在 CR 与 LF 之间),行内代码照留两字则逐字对回。 - 2026-10-15(用户报:ElyMark 源码模式“测”后的插入点画在“测”的中间):组件库 `CodeEditor` 原按“列 × m 的宽”放插入点、选区、按下处与输入法框,中日韩字与表情约宽两格。组件库 9fb607b 起按 gpui 排好的这一行定位(`editor::measure`,含着色带来的字重),块状与下划线插入点取所在字的宽;标尺与缩进参考线仍按列(R6.8)。上下移动仍按字数记目标列。cd61653:按下处取最近的字首或行末(gpui 的 `closest_index_for_x` 越过末字字首即给行末);上次绘制得出的括号与配对位置只属当时的文字(`Buffer` 每次改动换一个戳),输入法改字后、下次绘制前的测量不用它们(原先把新字切进不同字体段,DirectWrite 崩溃)。 - 2026-10-15(用户报:ElyMark 三、四级标题不显眼):组件库 5954132 起标题字号随正文按 Typora 的倍数(一至六级 2.25、1.75、1.5、1.25、1、1),一律加粗,六级为次要色,行高为字号的 1.3 倍(原为固定的 24、20、16、14 像素,正文 13,三、四级只是中等字重)(R6.9)。90bd37b:粗体与标题同一字重(原粗体为半粗,标题里的粗体反而更细,正文粗体随之由半粗改为粗,同 Typora);标题行高按自身字号取 1.3 倍。Windows 真窗口核对(`.work/r69`):可视模式各级标题分明、“你好↵测试”两行,源码模式“测”后的插入点落在两字之间。 -- 2026-10-15(用户报:macOS 打开一个文档,程序坞里有两个同样的图标):LOK worker 在应用包的 `Contents/MacOS` 里启动 AppKit,系统按包的 Info.plist 把它也登记为常规应用(`lsappinfo` 里应用与 worker 都是 Foreground),程序坞多一个图标;内核自己从不设激活策略。worker 在内核启动 AppKit 之前(`Lok::hook` 打开内核之后、调钩子之前)把本进程主包信息里的 `LSUIElement` 设为真,AppKit 登记时即为辅助应用(无程序坞图标、无菜单栏,仍可建屏外窗口);钩子之后核对激活策略是辅助,不是即报错、不启动(`ffi/accessory.rs`)。原先在登记之后才改策略,审查实测 15 次启动有 11 次先登记为 Foreground 约 30 毫秒(R6.12 第 1 轮)。`tests/scripts/dock-macos.sh` 连续启动三个应用,从 worker 启动起采样其类型到首块图块,见 Foreground 即失败(R6.12)。 +- 2026-10-15(用户报:macOS 打开一个文档,程序坞里有两个同样的图标):LOK worker 在应用包的 `Contents/MacOS` 里启动 AppKit,系统按包的 Info.plist 把它也登记为常规应用(`lsappinfo` 里应用与 worker 都是 Foreground),程序坞多一个图标;内核自己从不设激活策略。worker 在内核启动 AppKit 之前(`Lok::hook` 打开内核之后、调钩子之前)把本进程主包信息里的 `LSUIElement` 设为真,AppKit 登记时即为辅助应用(无程序坞图标、无菜单栏,仍可建屏外窗口);钩子之后核对激活策略是辅助,不是即报错、不启动(`ffi/accessory.rs`);包外的 worker(`cargo run`、单测)无包可登记,策略为禁止,此时改设辅助(R6.12 第 2 轮)。原先在登记之后才改策略,审查实测 15 次启动有 11 次先登记为 Foreground 约 30 毫秒(R6.12 第 1 轮)。`tests/scripts/dock-macos.sh` 连续启动三个应用与包外的开发构建,从 worker 启动起采样其类型到首块图块,见 Foreground 即失败(R6.12)。 - 2026-10-15(用户报:ElyShow 编辑区滚轮与上下键不换页,三端):内核在未选中对象时把上下键当作滚动窗口(`fupoor.cxx`),Ely 的画布整页适合、无处可滚,滚轮同样。`LokView::turn_parts`(ElyShow 首次适合时调用)之后:整页可见时滚轮换页(`view/turn.rs`:每格一页;触控板一次手势只换一页,惯性不算,静止 250 毫秒或新手势再计),未选中对象、未编辑文字时上下键换页;视图发 `Turn`,ElyShow 从视图所示的页起算(连续换页累加),经缩略图条同一路径 `show_part`,到首末页即停;后台读回的当前页只在视图已无待落地输入时采用,排队的换页不被先前的读取拉回(R6.13)。 diff --git a/crates/ely-lok/src/ffi/accessory.rs b/crates/ely-lok/src/ffi/accessory.rs index 932ad21..7c8ada4 100644 --- a/crates/ely-lok/src/ffi/accessory.rs +++ b/crates/ely-lok/src/ffi/accessory.rs @@ -13,6 +13,8 @@ unsafe extern "C" { /// NSApplicationActivationPolicyAccessory: no Dock icon, no menu bar. const ACCESSORY: isize = 1; +/// NSApplicationActivationPolicyProhibited: an unbundled worker starts so. +const PROHIBITED: isize = 2; type Id = *mut c_void; @@ -38,7 +40,7 @@ fn get(object: Id, selector: &std::ffi::CStr) -> io::Result { Ok(result) } -/// Marks this process a UI element before the kit registers it. +/// Marks the process a UI element before registration. pub fn hide() -> io::Result<()> { // The kit reads the bundle's LSUIElement when it registers. let info = get(get(class(c"NSBundle")?, c"mainBundle")?, c"infoDictionary")?; @@ -59,9 +61,18 @@ pub fn hide() -> io::Result<()> { /// Fails unless the kernel's application came up an accessory. pub fn check() -> io::Result<()> { type Policy = unsafe extern "C" fn(Id, Id) -> isize; + type SetPolicy = unsafe extern "C" fn(Id, Id, isize) -> i8; let policy: Policy = unsafe { std::mem::transmute(send()) }; + let set: SetPolicy = unsafe { std::mem::transmute(send()) }; let app = get(class(c"NSApplication")?, c"sharedApplication")?; - let found = unsafe { policy(app, sel_registerName(c"activationPolicy".as_ptr())) }; + let read = || unsafe { policy(app, sel_registerName(c"activationPolicy".as_ptr())) }; + let mut found = read(); + if found == PROHIBITED { + // Outside a bundle nothing registers; accessory lets it draw. + let done = unsafe { set(app, sel_registerName(c"setActivationPolicy:".as_ptr()), ACCESSORY) }; + found = read(); + log::info!("lok: the unbundled worker asked for accessory ({done})"); + } if found != ACCESSORY { return Err(io::Error::other(format!("the worker's activation policy is {found}, not accessory"))); } diff --git a/tasks/reviews/R6.12-r2.md b/tasks/reviews/R6.12-r2.md new file mode 100644 index 0000000..1536478 --- /dev/null +++ b/tasks/reviews/R6.12-r2.md @@ -0,0 +1,5 @@ +FAIL + +- crates/ely-lok/src/ffi/accessory.rs:65 — Unbundled macOS workers now fail during startup. Setting the main bundle dictionary's `LSUIElement` leaves their activation policy at 2, which this check rejects. On macOS 27.0.1 arm64, the independently built round 2 worker exited 1 before answering its first IPC request with `the worker's activation policy is 2, not accessory`; the independently built round 1 worker, using the same engine and fresh profiles, loaded and rendered Writer, Calc and Impress documents and shut down successfully (`.work/R6.12-r2/unbundled-r2-worker.log`, `unbundled-r1.log`). This breaks the standalone worker layout used by `cargo run` and the macOS worker tests. Establish an appropriate policy for unbundled workers while retaining the pre-registration bundle fix, and cover both launch layouts in the regression test. + +- crates/ely-lok/src/ffi/accessory.rs:41 — The new documentation comment contains eleven words, exceeding the explicit ten-word limit. Running `python tests/scripts/comments.py` exits 1 and identifies this line (`.work/R6.12-r2/comments.log`). Shorten it to ten words or fewer, or remove it. diff --git a/tests/scripts/dock-macos.sh b/tests/scripts/dock-macos.sh index 78d5e3b..2c46353 100755 --- a/tests/scripts/dock-macos.sh +++ b/tests/scripts/dock-macos.sh @@ -4,7 +4,8 @@ set -euo pipefail [ "$(uname)" = Darwin ] || { echo "SKIP: macOS only"; exit 0; } runs=${ELY_DOCK_RUNS:-5} apps=("$@") -[ ${#apps[@]} -gt 0 ] || apps=(dist/ElyDoc.app dist/ElyTable.app dist/ElyShow.app) +# Bundled apps from dist and one unbundled dev build. +[ ${#apps[@]} -gt 0 ] || apps=(dist/ElyDoc.app dist/ElyTable.app dist/ElyShow.app target/debug/ely-doc) # One launch: samples the worker's type until a tile shows. launch() { @@ -35,9 +36,14 @@ launch() { } for app in "${apps[@]}"; do - [ -d "$app" ] || { echo "FAIL: $app is missing"; exit 1; } - bin=$(find "$app/Contents/MacOS" -maxdepth 1 -type f -name 'ely-*' ! -name 'ely-lok-worker' | head -1) - [ -n "$bin" ] || { echo "FAIL: $app has no app binary"; exit 1; } + if [ -f "$app" ]; then + bin=$app + [ -x "$(dirname "$app")/ely-lok-worker" ] || { echo "FAIL: no worker beside $app (cargo build -p ely-lok --bins)"; exit 1; } + else + [ -d "$app" ] || { echo "FAIL: $app is missing"; exit 1; } + bin=$(find "$app/Contents/MacOS" -maxdepth 1 -type f -name 'ely-*' ! -name 'ely-lok-worker' | head -1) + [ -n "$bin" ] || { echo "FAIL: $app has no app binary"; exit 1; } + fi log="$HOME/Library/Application Support/ElyOffice/$(basename "$bin")/ely.log" for _ in $(seq 1 "$runs"); do launch "$bin" "$log"; done done