diff --git a/Cargo.lock b/Cargo.lock index 8532555..b16679c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3346,8 +3346,6 @@ dependencies = [ [[package]] name = "gpui-pre-windows" version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "371f9bb13f97e3bea2f8b8e5e81232bb74d735153c244ae67f7400e743925b1c" dependencies = [ "accesskit", "accesskit_windows", diff --git a/Cargo.toml b/Cargo.toml index dea4df4..8e714fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,6 @@ ely-ui = { path = "crates/ely-ui" } [workspace.lints.clippy] all = { level = "warn", priority = -1 } + +[patch.crates-io] +gpui-pre-windows = { path = "vendor/gpui-pre-windows" } diff --git a/agents.md b/agents.md index f9b6adc..c90a85f 100644 --- a/agents.md +++ b/agents.md @@ -27,6 +27,7 @@ crates/ ely-ui ely-platform ely-ipc(应用与引擎 worker 之间的帧 reference/ 上游源码,只读,不属于本项目,不审查 engines/ 下载的二进制与引擎构建产物,不入库,不审查;mupdf-src 是 reference/mupdf 加 patches/mupdf 的构建副本;content/<应用> 是网页内容的构建产物 patches/ 对上游的补丁,随源码发行(libreoffice、mupdf) +vendor/ crates.io 上游 crate 的副本加本项目的修复,经工作区 `[patch.crates-io]` 换入(gpui-pre-windows);首个提交为原样,其后是修复 packaging/ 三端打包脚本,apps.tsv 是八个应用的二进制名、名字、桌面分类与发布批次;icons/ 由图标母版渲染各端格式;libreoffice/ely.xcd 是内核的默认配置层 assets/ icons/ 是八个应用的图标母版(SVG)与渲染出的 icns、png、ico,规则见其 README dist/ 打包产物,不入库 @@ -283,4 +284,5 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely - 2026-10-10:ElyMark 的链接(M-05,我定,同常见编辑器):可视模式单击链接是编辑,Cmd 单击(Windows、Linux 为 Ctrl)交应用;预览单击即交应用;应用照 ElyPDF:http、https、mailto 交系统打开,其余提示拒绝。 - 2026-10-10:Windows 与 Linux 的构建环境(E-01)。Windows:MSVC(VS 2022,v143);MuPDF 由 `build-mupdf.sh` 以 MSBuild 编上游 `platform/win32` 的 `libmupdf`(静态运行库 /MT,同 Rust 的 `crt-static`;`HAVE_LIBCRYPTO`,`libmupdf.lib` 内含第三方、Tesseract 与 PKCS#7);静态 OpenSSL 取自钉在 2026.07.29 的 vcpkg(`engines/vcpkg`,`openssl:x64-windows-static`);Git 为 Git for Windows 的 MinGit 2.56.0(钉散列,`engines/git/cmd/git.exe`,LFS 在 `ucrt64/libexec/git-core`);CEF 照 cef-dll-sys 放在可执行文件旁(Windows、Linux 都是),不另载入;`%TEMP%` 下的程序被系统的应用控制拦下,构建与探针都不放那里;cmake 与 ninja 须在 PATH;LibreOffice 的 configure 只认 VS 自带的 CMake(组件 `VC.CMake.Project`)。仓库与 `reference/` 一律 LF(`core.autocrlf false`、`core.eol lf`;`fetch-references.sh` 对每个上游照设)。Linux:WSL 的 Ubuntu 26.04;静态 libcrypto 另需其 pkg-config 列出的库(jitterentropy、zstd、z)。LibreOffice 在 Linux 照 Collabora 的 LOKit 配置构建(无图形后端、cairo RGBA、自带各库)。 - 2026-10-10:Windows 与 Linux 的内核与 worker(W-01、K-01,用户定:0.1.3 先三端发布)。内核只认合并库(`ffi::KERNEL`:`libmergedlo.dylib`、`libmergedlo.so`、`mergedlo.dll`;`sofficeapp` 在合并构建里是文本占位);Windows 载入时一并搜索该 DLL 所在的 `program/`。查找次序照旧,Windows 与 Linux 的包内位置是应用旁的 `libreoffice/program`,开发构建为 `engines/lo-src/instdir/program`。worker 的主循环:macOS 照旧以 GCD;Linux 以 unipoll,`runLoop` 的轮询回调里每次答一个请求,内核的唤醒回调往同一队列放信号(`worker/unipoll.rs`;回调的数据指针须非空,VCL 见空即不调用);Windows 也以 unipoll:LOK 不另起主线程,worker 的主线程跑 `runLoop`(Windows 的消息循环),请求经只收消息的窗口投到这条线程上执行(`worker/messages.rs`),同 macOS 的 GCD;原先 LOK 自起主线程、worker 在另一线程作答,载入表格即死锁。Windows 的循环不调轮询回调,但须传非空的回调,`isUnipoll` 只看它。补丁 `0043-lok-callbacks-flush-inline-everywhere.patch`:回调队列在各平台都即时冲出(原只 macOS),否则请求里等的命令结果与状态要等下一轮主循环才到。`0042-svx-boost-json-needs-no-library.patch`:svxcore 与 sd 定义 `BOOST_ALL_NO_LIB`,MSVC 不再按 Boost 的自动链接索要 `libboost_json`、`libboost_container`。Linux 的内核以 cairo 画图块(worker 设 `SAL_DISABLESKIA`;构建去掉 `--enable-cairo-rgba`,自带的 cairo 要清掉重建),字节为 BGRA,同 macOS 与 Windows;`getTileMode` 只看 cairo 开关,不可信。worker 启动核对在三端都画一段红字,按 BGRA 找不到红色即拒绝。`scripts/apply-lo-patches.sh` 的临时库设 `core.autocrlf false`(Windows 默认转换使任何前缀都对不上)。Windows 的 LibreOffice 在 Cygwin 下构建(`build-libreoffice.sh` 的 CYGWIN 分支:去掉 Unix 专用开关,加 VS 2022,用 `/opt/lo/bin` 的 make,清掉 Git Bash 带进来的 `MSYSTEM`);构建目录与 Cygwin 排除在 Defender 扫描之外(否则并行 configure 删不掉 `conftest.dir`)。英文词典扩展里的 Lightproof(Python 写的语法检查)在构建后删去:macOS 与 Linux 的内核不带 Python,它本不生效;Windows 必带内置 Python(Cygwin 下 configure 不许去掉),第二次用同一配置启动时初始化它,worker 挂起。worker 以 `SAL_NOLOCK_PROFILE` 运行,`ely.xcd` 关掉 LibreOffice 的文档锁(`Common/Misc/UseLocking`):被强杀的 worker 留下的锁会让下次启动或载入弹出交互而挂起;每个应用只有一个 worker 用自己的配置,冲突由 Ely 自己核对。三端发布(CI-01):`.gitea/workflows/release.yml` 在打标签时于三台运行器上执行 `scripts/ci.sh`:macOS 在 Mac mini 的常驻克隆里按需从零构建引擎,Windows 在本机运行器上把 `engines` 与 `reference` 接到开发克隆,Linux 由同一台 Windows 运行器经 WSL 构建(服务器上的 Linux 运行器是一次性容器,建不了内核);各自打包后上传到该标签的发布。Windows 为 NSIS 安装程序,Linux 为 AppImage,都由 `packaging/stage.sh` 分装引擎;更新清单只含 macOS(Windows 与 Linux 的自动安装未做)。 +- 2026-10-11:gpui 的 Windows 窗口关闭(W-03 冒烟查出)。`gpui-pre-windows` 0.3.8 在窗口关闭时报三条错:系统已销毁窗口(ElyTree 的关窗交给系统)后 Drop 仍对旧句柄 `RevokeDragDrop` 与 `DestroyWindow`(拖放处理器因此不释放,连带窗口状态);销毁时的失活回报排在异步任务里,执行时窗口已移除(“window not found”);退出后后台任务向已销毁的平台窗口投递(`Invalid window handle`)。修复在 `vendor/gpui-pre-windows`:`WM_DESTROY` 时记下已销毁、在句柄仍有效时撤销拖放、去掉失活回调,Drop 见已销毁即不再动句柄;平台窗口已不在时投递失败只记 debug(应用已退出),其余投递失败照报。三个应用关窗与退出的日志无错。 - 2026-10-09(用户定):代码与发布从 GitHub 搬到自建的 Gitea(git.zacharyzhang.com;GitHub 账号被封,推送与发布都被拒)。公开仓库 `ZacharyZhang-NY/ElyOffice`(原样推上 main、缺陷清单分支与 v0.1.0、v0.1.1 标签),组件库为同站的 `ZacharyZhang-NY/Ely-GPUI-Components`(Cargo 的 git 依赖与 `fetch-references.sh` 随之改址,钉的提交不变)。v0.1.1 的六个 dmg 与两个源码包重新发在 Gitea 的 v0.1.1 发布里。官网的下载与源码链接指向 Gitea(`releases/latest` 会跳到最新发布页)。`.github/workflows` 删去(它们靠 GitHub 的 macOS 机器与 `gh`,发布改在本机以 `packaging/macos/release.sh` 构建、经 Gitea API 上传)。发布视频里写的地址已在源文件改为 Gitea,已发布的成片仍是旧地址,重渲染后才更新。上游项目(LibreOffice、MuPDF、git-lfs、tessdata 等)照旧从它们自己的 GitHub 地址取。 diff --git a/vendor/gpui-pre-windows/src/dispatcher.rs b/vendor/gpui-pre-windows/src/dispatcher.rs index 55f18b5..325c3a9 100644 --- a/vendor/gpui-pre-windows/src/dispatcher.rs +++ b/vendor/gpui-pre-windows/src/dispatcher.rs @@ -9,7 +9,7 @@ use std::{ use anyhow::Context; use gpui_util::ResultExt; use windows::Win32::{ - Foundation::{FILETIME, LPARAM, WPARAM}, + Foundation::{ERROR_INVALID_WINDOW_HANDLE, FILETIME, LPARAM, WPARAM}, Media::{timeBeginPeriod, timeEndPeriod}, System::Threading::{ CloseThreadpoolTimer, CreateThreadpoolTimer, GetCurrentThread, PTP_CALLBACK_INSTANCE, @@ -119,14 +119,22 @@ impl PlatformDispatcher for WindowsDispatcher { match self.main_sender.send(priority, runnable) { Ok(_) => { if !self.wake_posted.swap(true, Ordering::AcqRel) { - unsafe { + let posted = unsafe { PostMessageW( Some(self.platform_window_handle.as_raw()), WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD, WPARAM(self.validation_number), LPARAM(0), ) - .log_err(); + }; + match posted { + // The platform window is gone: the app has quit. + Err(e) if e.code() == ERROR_INVALID_WINDOW_HANDLE.to_hresult() => { + log::debug!("main-thread task after quit, dropped"); + } + result => { + result.log_err(); + } } } } diff --git a/vendor/gpui-pre-windows/src/events.rs b/vendor/gpui-pre-windows/src/events.rs index 4a9b18b..97dc648 100644 --- a/vendor/gpui-pre-windows/src/events.rs +++ b/vendor/gpui-pre-windows/src/events.rs @@ -341,6 +341,10 @@ impl WindowsWindowInner { } fn handle_destroy_msg(&self, handle: HWND) -> Option { + self.state.destroyed.set(true); + unsafe { windows::Win32::System::Ole::RevokeDragDrop(handle).log_err() }; + // Its deferred report would find no window. + self.state.callbacks.active_status_change.take(); let callback = { self.state.callbacks.close.take() }; // Re-enable parent window if this was a modal dialog if let Some(parent_hwnd) = self.parent_hwnd { diff --git a/vendor/gpui-pre-windows/src/window.rs b/vendor/gpui-pre-windows/src/window.rs index 8a5327d..29924c2 100644 --- a/vendor/gpui-pre-windows/src/window.rs +++ b/vendor/gpui-pre-windows/src/window.rs @@ -62,6 +62,7 @@ pub struct WindowsWindowState { pub last_reported_capslock: Cell>, pub hovered: Cell, pub last_visibility: Cell>, + pub destroyed: Cell, pub direct_manipulation: DirectManipulationHandler, pub renderer: RefCell, @@ -177,6 +178,7 @@ impl WindowsWindowState { last_reported_capslock: Cell::new(last_reported_capslock), hovered: Cell::new(hovered), last_visibility: Cell::new(None), + destroyed: Cell::new(false), renderer: RefCell::new(renderer), force_render_pending: Cell::new(false), click_state, @@ -610,10 +612,14 @@ impl rwh::HasDisplayHandle for WindowsWindow { impl Drop for WindowsWindow { fn drop(&mut self) { self.0.dialog_owner.close(); - unsafe { ShowWindowAsync(self.0.hwnd, SW_HIDE).ok().log_err() }; // `DestroyWindow` below sends `WM_SHOWWINDOW`; without a callback the // resulting visibility report has nothing to notify. self.0.state.callbacks.visibility_change.take(); + // Windows already destroyed it. + if self.0.state.destroyed.get() { + return; + } + unsafe { ShowWindowAsync(self.0.hwnd, SW_HIDE).ok().log_err() }; // clone this `Rc` to prevent early release of the pointer let this = self.0.clone(); self.0 @@ -621,10 +627,7 @@ impl Drop for WindowsWindow { .spawn(async move { this.dialog_owner.when_idle().await; let handle = this.hwnd; - unsafe { - RevokeDragDrop(handle).log_err(); - DestroyWindow(handle).log_err(); - } + unsafe { DestroyWindow(handle).log_err() }; }) .detach(); }