diff --git a/apps/pdftools/README.md b/apps/pdftools/README.md index 1a5da424..ddc5d00d 100644 --- a/apps/pdftools/README.md +++ b/apps/pdftools/README.md @@ -59,7 +59,7 @@ page fits the canvas, up to 100%. | --- | --- | --- | | Home | Recent PDFs as cards with their first page, pages, size and when they were opened; Open a PDF from this device (up to 64 MB); remove a PDF (asks once more); an empty state on a first run | `pdf.open`, `pdf.info`, `pdf.page`, `pdf.close` | | Read | Pages render one at a time at 72 dpi x zoom x 2 and are kept; thumbnails fill in one by one; the outline jumps to a section | `pdf.open`, `pdf.page` | -| Read mode | The pill's full-screen button: the pages alone. The tabs, the mode bar, the rail and both panels, the tool row and the status line give the pages their room, and the pill stays with previous and next, the page, the zoom, fit width and fit page, and Exit read mode. A quiet button at the top right leaves too (a script gets no key events, so there is no Esc). The page and the zoom you chose carry across entering and leaving; a fitted or automatic zoom fits the new canvas. A tap on a page only chooses it | `pdf.page` | +| Read mode | The pill's full-screen button: the pages alone. The tabs, the mode bar, the rail and both panels, the tool row and the status line give the pages their room, and the pill stays with previous and next, the page, the zoom, fit width and fit page, and Exit read mode. In a desktop shell the same tap puts the window in full screen too (`window.fullscreen`): the pages fill the screen, with no bar, title bar or dock, and no screen edge brings them back. There Esc, the shell's key (a script gets no key events), leaves Read mode, and so do ⌘F and another window taking the focus (`on_app_fullscreen`). A quiet button at the top right leaves too. Where the shell can't (a tap it doesn't count, or a host with no window service, such as card-host), Read mode fills the window, without a word. The page and the zoom you chose carry across entering and leaving; a fitted or automatic zoom fits the new canvas. A tap on a page only chooses it | `pdf.page` | | Find | Search in document, the one way to search, then Return: "12 matches on 7 pages" in the left panel, the results grouped by page with the match in bold, every match highlighted on its page, the current one stronger, previous and next. Done, the results' cross or the field's cross ends the search, and the left panel shows what it did before (Pages if it showed nothing known); a change to the document ends it the same way | `pdf.find` | | Comment | Highlight, Underline or Strike out the line you tap (tap twice for its paragraph), Note and Text box where you tap, in one of four colours; threads with replies, a status, delete. Comments carry the name kept under "Comment as" | `pdf.lines`, `pdf.comments`, `pdf.comment` | | Fill & Sign | The form's fields outlined (required empty ones in red) and listed with their values; choose one to type its value; Add text, Add date and Add initials place a mark where you tap; initials are kept | `pdf.fields`, `pdf.fill`, `pdf.fill_sign` | @@ -247,7 +247,7 @@ exceeded` in its log); `--budget-ms 1000` raises the hosts' budget | Run | What it covers | | --- | --- | -| `shell` | Every mode on the samples in the shell's own window, light, then dark through the shell's own style menu (the dark pass works on what the light pass left): Home and its covers, the status line against the storage's own bytes, "Open a PDF from this device" refused (on Linux with no dialog helper, "Not available here"), reading and thumbnails, Read mode (entered from the pill, a page turned and the zoom changed in it, left by Exit read mode or the corner control on that page at that zoom), find ("9 matches on 4 pages") from the top bar alone, with the left panel given back when it ends, a highlight and its reply dated on this machine's clock ("Today 19:42"), a rotation and its undo, combine with a page range ("One PDF of 5 pages"), initials placed by Fill & Sign, a text edit with an alignment and a colour, save, and the damaged file; then one cover per PDF in the cover cache, each in `library.json` | +| `shell` | Every mode on the samples in the shell's own window, light, then dark through the shell's own style menu (the dark pass works on what the light pass left): Home and its covers, the status line against the storage's own bytes, "Open a PDF from this device" refused (on Linux with no dialog helper, "Not available here"), reading and thumbnails, Read mode (entered from the pill, the window in full screen while it lasts, a page turned and the zoom changed in it, left by Exit read mode or the corner control on that page at that zoom, with the window back; then left by Esc), find ("9 matches on 4 pages") from the top bar alone, with the left panel given back when it ends, a highlight and its reply dated on this machine's clock ("Today 19:42"), a rotation and its undo, combine with a page range ("One PDF of 5 pages"), initials placed by Fill & Sign, a text edit with an alignment and a colour, save, and the damaged file; then one cover per PDF in the cover cache, each in `library.json` | | `restart` | The same home again: the library, its titles, the page a PDF was left at, `Imported PDF.pdf` placed as an import leaves one, every current cover kept, not drawn again, and a PDF removed from Home taking its kept cover with it | | `full` | Storage filled to 8 KB short of its 64 MiB: the refusal on Home, then a removal, which asks once more | | `empty` | No PDFs: the empty library and its Open button, light and dark | @@ -325,16 +325,28 @@ they leave the step out and say so in `run.json`. `--edit-sample` page: the five-pieces note, the font and room warnings with Apply anyway, the plain refusal of Chinese text, an edit made anyway and one made at once. Edit on macOS since this change is **unverified**. +- **Verified on 13 Oct 2026, Read mode in full screen** (the Linux build + host: Ubuntu 26.04, x86_64, a private headless weston; + [tests/evidence/read-fullscreen-20261013](tests/evidence/read-fullscreen-20261013/README.md)): + the model tests; the `shell` run with the real engine at the runtime's + 64 ms budget, light and dark, on #520 at `18c11642`. The pill's + full-screen button put the window over the shell's whole window, Exit + read mode and the corner control gave it back on the same page at the + same zoom, and Esc ended both the full screen and Read mode. These are + **unverified**: the same on macOS; the OctoSense window entering the + host's full screen, which a hidden shell never asks for; and card-host, + which serves no window service, so Read mode there should fill its view + as before without a word. - **Not verified**: the host's own file dialog (a hidden window on the Mac cannot open it; on Linux a stand-in helper answered); with the real engine, form fields, outlines, Redo, the other comment and page operations, password-protected PDFs, the eight-PDF cap and the unsaved-changes question (the dev fixture covers their screens); other screen sizes; on Linux, X11, a GPU and a desktop compositor; Windows; phones. -- **Known**: Read mode fills the app's window, not the screen: no app can - make the OctoSense window itself full screen (there is no host API for it), - so maximize the window for the full effect. On a heavily loaded machine a - tap can overrun the 64 ms budget +- **Known**: Read mode's full screen asks the shell to put the OctoSense + window itself in full screen as well; a hidden test shell never asks the + host window, so that step on a real display is **unverified**. On a + heavily loaded machine a tap can overrun the 64 ms budget and be lost (seen at load averages of 60 to 150 before `library.json` moved off the tap path). The pdf service's file writes run on the UI thread and count against that budget too (overruns after 262 and 555 ms renames, at diff --git a/apps/pdftools/README.zh-CN.md b/apps/pdftools/README.zh-CN.md index d0d10a5d..642fb230 100644 --- a/apps/pdftools/README.zh-CN.md +++ b/apps/pdftools/README.zh-CN.md @@ -48,7 +48,7 @@ PDF Tools 以 1296 x 703 点打开,应用区域为 1292 x 667,即它的宽 | --- | --- | --- | | 主页 | 最近的 PDF 以卡片显示首页、页数、大小和打开时间;Open a PDF from this device(最大 64 MB);移除 PDF(会再确认一次);首次运行时的空状态 | `pdf.open`、`pdf.info`、`pdf.page`、`pdf.close` | | Read | 页面按 72 dpi × 缩放 × 2 逐页渲染并保留;缩略图逐个填入;大纲可跳转到章节 | `pdf.open`、`pdf.page` | -| 阅读模式 | 浮动控制条上的全屏按钮:只显示页面。文档标签、模式栏、侧栏和两侧面板、工具行以及状态栏都让出空间,浮动控制条保留上一页和下一页、页码、缩放、适合宽度、适合页面以及 Exit read mode。右上角一个不显眼的按钮也可以退出(脚本收不到按键事件,因此没有 Esc)。进入和退出时,当前页和你选择的缩放保持不变;适合宽度、适合页面或自动缩放会适应新的画布。在页面上点按只会选中该页 | `pdf.page` | +| 阅读模式 | 浮动控制条上的全屏按钮:只显示页面。文档标签、模式栏、侧栏和两侧面板、工具行以及状态栏都让出空间,浮动控制条保留上一页和下一页、页码、缩放、适合宽度、适合页面以及 Exit read mode。在桌面 Shell 中,同一次点按还会让窗口进入全屏(`window.fullscreen`):页面占满屏幕,没有栏、标题栏或程序坞,屏幕边缘也不会把它们唤出。此时按 Esc(这是 Shell 的按键,脚本收不到按键事件)会退出阅读模式,⌘F 以及其他窗口获得焦点时也会退出(`on_app_fullscreen`)。右上角一个不显眼的按钮也可以退出。Shell 做不到时(点按不被计入,或宿主没有窗口服务,例如 card-host),阅读模式像以前一样占满窗口,不显示任何提示。进入和退出时,当前页和你选择的缩放保持不变;适合宽度、适合页面或自动缩放会适应新的画布。在页面上点按只会选中该页 | `pdf.page` | | 查找 | Search in document 是唯一的查找入口,输入后按 Return:左侧面板显示“12 matches on 7 pages”,结果按页分组、匹配处加粗,每处匹配在页面上高亮,当前匹配更醒目,可前后切换。按 Done、结果面板的关闭按钮或搜索框的清除按钮结束查找后,左侧面板恢复之前显示的内容(之前的内容不明时显示 Pages);文档发生更改时也以同样方式结束查找 | `pdf.find` | | Comment | 对点按的那一行做高亮、下划线或删除线(点按两次作用于整段),在点按处添加 Note 和 Text box,四种颜色可选;带回复、状态和删除的评论线程。评论使用在“Comment as”中保存的名字 | `pdf.lines`、`pdf.comments`、`pdf.comment` | | Fill & Sign | 用框线标出表单字段(未填的必填字段为红色)并列出其值;选中一个字段即可输入值;Add text、Add date 和 Add initials 会在点按处放置标记;姓名缩写会被保存 | `pdf.fields`、`pdf.fill`、`pdf.fill_sign` | @@ -196,7 +196,7 @@ python3 apps/pdftools/tests/ui.py --shell target/debug/octosense --lock -- | 运行 | 覆盖内容 | | --- | --- | -| `shell` | 在 Shell 自己的窗口中、在示例上覆盖每种模式,先浅色,再通过 Shell 自己的样式菜单切到深色(深色这一轮在浅色一轮留下的状态上进行):主页及其封面、状态栏与存储实际字节数相符、“Open a PDF from this device”被拒绝(在没有对话框助手程序的 Linux 上为“Not available here”)、阅读和缩略图、阅读模式(从浮动控制条进入,在其中翻页并改变缩放,再用 Exit read mode 或右上角按钮退出,回到同一页、同一缩放)、只用顶栏的查找(“9 matches on 4 pages”,结束时交还左侧面板)、一处高亮及其回复(日期按本机时钟显示,如“Today 19:42”)、一次旋转及其撤销、带页面范围的合并(“One PDF of 5 pages”)、Fill & Sign 放置的姓名缩写、一次带对齐方式和颜色的文字编辑、保存,以及损坏的文件;之后检查封面缓存中每个 PDF 只有一个封面,且都记在 `library.json` 中 | +| `shell` | 在 Shell 自己的窗口中、在示例上覆盖每种模式,先浅色,再通过 Shell 自己的样式菜单切到深色(深色这一轮在浅色一轮留下的状态上进行):主页及其封面、状态栏与存储实际字节数相符、“Open a PDF from this device”被拒绝(在没有对话框助手程序的 Linux 上为“Not available here”)、阅读和缩略图、阅读模式(从浮动控制条进入,期间窗口处于全屏,在其中翻页并改变缩放,再用 Exit read mode 或右上角按钮退出,回到同一页、同一缩放,窗口恢复原位;之后再用 Esc 退出一次)、只用顶栏的查找(“9 matches on 4 pages”,结束时交还左侧面板)、一处高亮及其回复(日期按本机时钟显示,如“Today 19:42”)、一次旋转及其撤销、带页面范围的合并(“One PDF of 5 pages”)、Fill & Sign 放置的姓名缩写、一次带对齐方式和颜色的文字编辑、保存,以及损坏的文件;之后检查封面缓存中每个 PDF 只有一个封面,且都记在 `library.json` 中 | | `restart` | 再次使用同一个主目录:资料库、其中的标题、PDF 离开时所在的页面、按导入方式放入的 `Imported PDF.pdf`、所有当前封面都被保留且没有重新绘制,以及从主页移除的 PDF 连同其保留的封面一起删除 | | `full` | 存储被填到距 64 MiB 只差 8 KB:主页上的拒绝提示,然后移除一个 PDF(会再确认一次) | | `empty` | 没有 PDF:空资料库及其 Open 按钮,浅色和深色 | @@ -257,12 +257,19 @@ python3 apps/pdftools/dev-fixture/make_fixture.py /os.pdftoo pdf 服务的测试;在桌面 Shell 中使用真实引擎、按运行时 64 ms 的预算,在 `--edit-sample` 页面上进行的浅色和深色运行:五块的说明、字体和空间的警告以及 Apply anyway、对中文文字的 直白拒绝、一次仍然进行的编辑和一次立即生效的编辑。本次改动后 macOS 上的 Edit **未验证**。 +- **已于 2026 年 10 月 13 日验证阅读模式全屏**(Linux 构建主机:Ubuntu 26.04,x86_64,私有的无头 + weston;[tests/evidence/read-fullscreen-20261013](tests/evidence/read-fullscreen-20261013/README.zh-CN.md)): + 模型测试;基于 #520 的 `18c11642`,使用真实引擎、按运行时 64 ms 的预算进行的 `shell` 运行,浅色和 + 深色。浮动控制条上的全屏按钮让窗口铺满 Shell 的整个窗口,Exit read mode 和右上角按钮让窗口恢复 + 原位,页码和缩放保持不变,Esc 同时结束全屏和阅读模式。以下**未验证**:在 macOS 上的同样运行; + OctoSense 窗口进入宿主全屏(隐藏窗口的 Shell 从不请求它);card-host(它不提供窗口服务,阅读模式 + 在那里应像以前一样占满它的视图,不显示任何提示)。 - **未验证**:宿主自己的文件对话框(Mac 上的隐藏窗口无法打开它;在 Linux 上由替身助手程序 作答);使用真实引擎时的表单字段、大纲、Redo、其他评论和页面操作、受密码保护的 PDF、八个 PDF 的上限以及未保存更改的询问(开发夹具覆盖了它们的界面);其他屏幕尺寸;Linux 上的 X11、GPU 和桌面合成器;Windows;手机。 -- **已知问题**:阅读模式占满的是应用窗口,而不是整个屏幕:应用无法让 OctoSense 窗口本身全屏 - (没有相应的宿主 API),想要完整效果请将窗口最大化。在负载很高的机器上,一次点按可能超出 +- **已知问题**:阅读模式的全屏会请 Shell 让 OctoSense 窗口本身也进入全屏;隐藏窗口的测试 Shell + 从不让宿主窗口进入全屏,所以这一步在真实显示器上**尚未验证**。在负载很高的机器上,一次点按可能超出 64 ms 预算而丢失(在 `library.json` 移出点按路径之前,于 60 到 150 的平均负载下见到过)。pdf 服务的文件写入在 UI 线程上运行, 也计入这个预算(在 54 到 74 的平均负载下,曾在 262 ms 和 555 ms 的重命名之后超出预算)。在 900 点高的屏幕上(应用区域约为 diff --git a/apps/pdftools/bundle/main.splash b/apps/pdftools/bundle/main.splash index d1b78705..95cfe0cb 100644 --- a/apps/pdftools/bundle/main.splash +++ b/apps/pdftools/bundle/main.splash @@ -609,6 +609,7 @@ if st == nil { busy_at: 0 // when the background render went out heartbeat: nil // the interval below, made once index_dirty: false // library.json waits for the heartbeat + screen_full: false // the shell has the window in full screen for Read mode (on_app_fullscreen) } mod.pdftools = st } @@ -1362,15 +1363,34 @@ fn refit(t){ fn reading_on(t){ t != nil && optional(t, "reading", false) && t.state == "ready" } // From the floating bar's full-screen button: the same page, and the zoom // the person chose, or the fit, worked out for the whole window (kept_zoom). +// The tap also asks the shell to put the window in full screen +// (window.fullscreen). Where it can't (no tap it counts, a host with no +// window service, such as card-host), Read mode fills the window as before, +// and the answer is not shown. fn enter_reading(t){ if t == nil || t.state != "ready" || t.mode == "pages" || t.mode == "combine" { return } t.reading = true reading_zoom(t) show() go_page(t, t.page) -} -// From the bar's Exit read mode or the corner control (a script gets no key -// events, so there is no Esc): the page and zoom it left Read mode with. + host.request("window.fullscreen", {on: true}, fn(r){}) +} +// The shell's word that the window's full screen started or ended: when +// Esc, ⌘F or another window ends it, Read mode ends too. One that starts +// after Read mode already ended goes straight back. +fn on_app_fullscreen(on){ + st.screen_full = on + if on { screen_back() } else { leave_reading(cur_tab()) } +} +// The full screen Read mode asked for goes back once Read mode is over, +// however it ended (show() asks after every change). +fn screen_back(){ + if !optional(st, "screen_full", false) || reading_on(cur_tab()) { return } + st.screen_full = false + host.request("window.fullscreen", {on: false}, fn(r){}) +} +// From the bar's Exit read mode or the corner control, or the shell's Esc +// (on_app_fullscreen): the page and zoom it left Read mode with. fn leave_reading(t){ if t == nil || !optional(t, "reading", false) { return } t.reading = false @@ -2506,6 +2526,7 @@ fn show(){ let t = cur_tab() let doc = t != nil let c = chrome_for(reading_on(t)) + screen_back() ui.home.set_visible(!doc) ui.doc.set_visible(doc) ui.tabs_box.set_visible(c.tabs) diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/02c-read-mode.json b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02c-read-mode.json new file mode 100644 index 00000000..422df989 --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02c-read-mode.json @@ -0,0 +1 @@ +{"area": [0, 0, 1400, 900], "window": [1400, 900]} \ No newline at end of file diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/02c-read-mode.png b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02c-read-mode.png new file mode 100644 index 00000000..25513834 Binary files /dev/null and b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02c-read-mode.png differ diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/02e-read-mode-left.json b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02e-read-mode-left.json new file mode 100644 index 00000000..02f7939e --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02e-read-mode-left.json @@ -0,0 +1 @@ +{"area": [54, 76, 1292, 662], "window": [1400, 900]} \ No newline at end of file diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/02e-read-mode-left.png b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02e-read-mode-left.png new file mode 100644 index 00000000..0cece398 Binary files /dev/null and b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02e-read-mode-left.png differ diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/02g-read-mode-escape.json b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02g-read-mode-escape.json new file mode 100644 index 00000000..02f7939e --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02g-read-mode-escape.json @@ -0,0 +1 @@ +{"area": [54, 76, 1292, 662], "window": [1400, 900]} \ No newline at end of file diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/02g-read-mode-escape.png b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02g-read-mode-escape.png new file mode 100644 index 00000000..b970caf6 Binary files /dev/null and b/apps/pdftools/tests/evidence/read-fullscreen-20261013/02g-read-mode-escape.png differ diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/README.md b/apps/pdftools/tests/evidence/read-fullscreen-20261013/README.md new file mode 100644 index 00000000..70c9a6c9 --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/README.md @@ -0,0 +1,39 @@ +# PDF Tools' Read mode in full screen — 2026-10-13 + +English | [简体中文](README.zh-CN.md) + +Read mode's tap now also asks the shell for `window.fullscreen {on: true}` +(#520), and leaving Read mode gives the window back. When the shell ends the +full screen (Esc, ⌘F, another window taking the focus), Read mode ends too. +A desktop shell built from this branch showed it with the real `pdf` engine: +`tests/ui.py`'s `shell` run, light then dark, on the Linux build host. + +- **Build:** `cargo build --locked -p octosense --no-default-features --features app-hub,craft-engines`, + from #520 at `18c11642` with this change's `main.splash` and `tests/ui.py` + (their SHA-1s and the binary's SHA-256 are in [receipt.json](receipt.json)). +- **Host:** Ubuntu 26.04, x86_64, a private headless weston 14.0.2 (pixman + renderer, never idle) with Makepad's Wayland backend. The shell's window + is 1400 x 900 points. +- **Run:** `python3 apps/pdftools/tests/ui.py --shell target/debug/octosense --lock --work --grab-scale 1 --only shell --output `. + Every step passed at the runtime's 64 ms script budget. The shell's log + has no panic and no script error. + +A hidden shell (`MAKEPAD_HIDE_WINDOWS`) never asks the host window to go full +screen, by design (#520), so "full screen" here is the app's window covering +the shell's whole window, with no bar, title bar or dock. Whether the +OctoSense window then enters macOS's full screen on a real display is +**unverified**. + +Frames are `/g` grabs of the whole window at one pixel per point. Each has a +`.json` with the app's area and the window's size. + +| Frame | What it shows | +| --- | --- | +| [02c-read-mode.png](02c-read-mode.png) | The pill's full-screen button on page 2 of 4: Read mode, and the window covers all of the 1400 x 900 (it was 1292 x 662 at 54, 76), with no bar, title bar or dock | +| [02e-read-mode-left.png](02e-read-mode-left.png) | A page turned to 3 / 4 and the zoom raised from 100% to 125% in Read mode, then Exit read mode: the window is back at 54, 76, 1292 x 662 with the bar and the dock, on 3 / 4 at 125% | +| [02g-read-mode-escape.png](02g-read-mode-escape.png) | Read mode again (the window covering all of it again), then Esc, the shell's key: the shell ends the full screen, the app hears `on_app_fullscreen(false)` and leaves Read mode on 3 / 4 at 100%, and the window is back | +| [d02c-read-mode.png](d02c-read-mode.png) | The dark pass: the same Read mode in full screen. The corner control left it, with the window back | + +The shell's log has three presentations. The first was ended by the app's +`{on: false}` after Exit read mode, the second by Esc (with no request after +it), and the third by the app's `{on: false}` after the corner control. diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/README.zh-CN.md b/apps/pdftools/tests/evidence/read-fullscreen-20261013/README.zh-CN.md new file mode 100644 index 00000000..15f4aa5e --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/README.zh-CN.md @@ -0,0 +1,32 @@ +# PDF Tools 阅读模式全屏 — 2026-10-13 + +[English](README.md) | 简体中文 + +进入阅读模式的那次点按现在还会请 Shell 执行 `window.fullscreen {on: true}`(#520),离开阅读模式时 +窗口恢复原位。Shell 结束全屏时(Esc、⌘F、其他窗口获得焦点),阅读模式也随之结束。用这个分支构建的 +桌面 Shell 在 Linux 构建主机上以真实的 `pdf` 引擎演示了这一点:`tests/ui.py` 的 `shell` 运行,先浅色 +再深色。 + +- **构建:**`cargo build --locked -p octosense --no-default-features --features app-hub,craft-engines`, + 基于 #520 的 `18c11642`,加上这次改动的 `main.splash` 和 `tests/ui.py`(它们的 SHA-1 以及二进制文件 + 的 SHA-256 见 [receipt.json](receipt.json))。 +- **主机:**Ubuntu 26.04,x86_64,私有的无头 weston 14.0.2(pixman 渲染器,从不空闲),使用 Makepad + 的 Wayland 后端。Shell 窗口为 1400 x 900 点。 +- **运行:**`python3 apps/pdftools/tests/ui.py --shell target/debug/octosense --lock --work --grab-scale 1 --only shell --output `。 + 每一步都在运行时默认的 64 ms 脚本预算内通过。Shell 日志中没有 panic,也没有脚本错误。 + +隐藏窗口的 Shell(`MAKEPAD_HIDE_WINDOWS`)按设计从不让宿主窗口进入全屏(#520),所以这里的“全屏”是 +应用窗口铺满 Shell 的整个窗口,没有栏、标题栏或程序坞。OctoSense 窗口随后是否会在真实显示器上进入 +macOS 全屏**尚未验证**。 + +画面是整个窗口的 `/g` 截取,每点一个像素。每张都附有一个 `.json`,记录应用区域和窗口尺寸。 + +| 画面 | 显示内容 | +| --- | --- | +| [02c-read-mode.png](02c-read-mode.png) | 在第 2 / 4 页按浮动控制条上的全屏按钮:进入阅读模式,窗口铺满整个 1400 x 900(之前在 54, 76,大小 1292 x 662),没有栏、标题栏或程序坞 | +| [02e-read-mode-left.png](02e-read-mode-left.png) | 在阅读模式中翻到 3 / 4 页、把缩放从 100% 调到 125%,然后按 Exit read mode:窗口回到 54, 76,大小 1292 x 662,栏和程序坞也回来了,仍在 3 / 4 页、125% | +| [02g-read-mode-escape.png](02g-read-mode-escape.png) | 再次进入阅读模式(窗口再次铺满),然后按 Esc(这是 Shell 的按键):Shell 结束全屏,应用收到 `on_app_fullscreen(false)` 并在 3 / 4 页、100% 处离开阅读模式,窗口恢复原位 | +| [d02c-read-mode.png](d02c-read-mode.png) | 深色这一轮:同样的阅读模式全屏。之后用右上角按钮离开,窗口恢复原位 | + +Shell 日志中有三次演示全屏。第一次由 Exit read mode 之后应用发出的 `{on: false}` 结束,第二次由 Esc +结束(之后没有再发出请求),第三次由右上角按钮之后应用发出的 `{on: false}` 结束。 diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/d02c-read-mode.json b/apps/pdftools/tests/evidence/read-fullscreen-20261013/d02c-read-mode.json new file mode 100644 index 00000000..422df989 --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/d02c-read-mode.json @@ -0,0 +1 @@ +{"area": [0, 0, 1400, 900], "window": [1400, 900]} \ No newline at end of file diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/d02c-read-mode.png b/apps/pdftools/tests/evidence/read-fullscreen-20261013/d02c-read-mode.png new file mode 100644 index 00000000..a4d0136a Binary files /dev/null and b/apps/pdftools/tests/evidence/read-fullscreen-20261013/d02c-read-mode.png differ diff --git a/apps/pdftools/tests/evidence/read-fullscreen-20261013/receipt.json b/apps/pdftools/tests/evidence/read-fullscreen-20261013/receipt.json new file mode 100644 index 00000000..89c05010 --- /dev/null +++ b/apps/pdftools/tests/evidence/read-fullscreen-20261013/receipt.json @@ -0,0 +1,169 @@ +{ + "result": "passed", + "date": "2026-10-13", + "platform": "Linux build host: Ubuntu 26.04 LTS, x86_64; a private headless weston 14.0.2 (pixman renderer, never idle), Makepad's Wayland backend", + "commit": "#520 at 18c11642 (feat/app-fullscreen) with this change's apps/pdftools/bundle/main.splash and apps/pdftools/tests/ui.py", + "source_sha1": { + "apps/pdftools/bundle/main.splash": "85b53408e460a574034d4a8105d2b01f8fb2efe5", + "apps/pdftools/tests/ui.py": "302515d35e11a264370e313e2c862f0f8caf3891" + }, + "build": "cargo build --locked -p octosense --no-default-features --features app-hub,craft-engines", + "binary_sha256": "1eb5fb9e8dc21973b60769d101fcba7a17a160a05dd2a93506a424e53a2bb7ec", + "run": "python3 apps/pdftools/tests/ui.py --shell target/debug/octosense --lock --work --grab-scale 1 --only shell --output ", + "script_budget_ms": 64, + "shell_log": { + "panics": 0, + "script_budget_overruns": 0, + "window_fullscreen_lines": [ + "wm: app full screen: os.pdftools (client 1); the host window is not asked to enter full screen: windows are hidden (MAKEPAD_HIDE_WINDOWS)", + "wm: window.fullscreen {on: true} from os.pdftools: {\"applied\":true,\"fills_screen\":true,\"fullscreen\":true}", + "wm: app full screen ended: os.pdftools (client 1), App", + "wm: window.fullscreen {on: false} from os.pdftools: {\"applied\":true,\"fills_screen\":false,\"fullscreen\":false}", + "wm: app full screen: os.pdftools (client 1); the host window is not asked to enter full screen: windows are hidden (MAKEPAD_HIDE_WINDOWS)", + "wm: window.fullscreen {on: true} from os.pdftools: {\"applied\":true,\"fills_screen\":true,\"fullscreen\":true}", + "wm: app full screen ended: os.pdftools (client 1), Escape", + "wm: app full screen: os.pdftools (client 1); the host window is not asked to enter full screen: windows are hidden (MAKEPAD_HIDE_WINDOWS)", + "wm: window.fullscreen {on: true} from os.pdftools: {\"applied\":true,\"fills_screen\":true,\"fullscreen\":true}", + "wm: app full screen ended: os.pdftools (client 1), App", + "wm: window.fullscreen {on: false} from os.pdftools: {\"applied\":true,\"fills_screen\":false,\"fullscreen\":false}" + ] + }, + "read_mode": [ + { + "pass": "light", + "entered_on": "2 / 4", + "left_on": "3 / 4", + "zoom": [ + 1.0, + 1.25 + ], + "left_by": "Exit read mode", + "window": [ + 54, + 76, + 1292, + 662 + ], + "full_screen": [ + 0, + 0, + 1400, + 900 + ], + "after": [ + 54, + 76, + 1292, + 662 + ] + }, + { + "pass": "dark", + "entered_on": "2 / 4", + "left_on": "3 / 4", + "zoom": [ + 1.0, + 1.25 + ], + "left_by": "corner control", + "window": [ + 54, + 76, + 1292, + 662 + ], + "full_screen": [ + 0, + 0, + 1400, + 900 + ], + "after": [ + 54, + 76, + 1292, + 662 + ] + } + ], + "read_mode_escape": [ + { + "window": [ + 54, + 76, + 1292, + 662 + ], + "full_screen": [ + 0, + 0, + 1400, + 900 + ], + "after_escape": [ + 54, + 76, + 1292, + 662 + ], + "page": "3 / 4", + "zoom": 1.0 + } + ], + "frames": { + "02c-read-mode.png": { + "area": [ + 0, + 0, + 1400, + 900 + ], + "window": [ + 1400, + 900 + ] + }, + "02e-read-mode-left.png": { + "area": [ + 54, + 76, + 1292, + 662 + ], + "window": [ + 1400, + 900 + ] + }, + "02g-read-mode-escape.png": { + "area": [ + 54, + 76, + 1292, + 662 + ], + "window": [ + 1400, + 900 + ] + }, + "d02c-read-mode.png": { + "area": [ + 0, + 0, + 1400, + 900 + ], + "window": [ + 1400, + 900 + ] + } + }, + "host_window": "never asked to enter full screen: MAKEPAD_HIDE_WINDOWS is set (#520's launch_input::hidden_windows)", + "not_verified": [ + "the same run on macOS", + "the OctoSense window entering the host's full screen on a real display", + "card-host, which serves no window service" + ] +} diff --git a/apps/pdftools/tests/ui.py b/apps/pdftools/tests/ui.py index e05957b0..a16a3bd6 100644 --- a/apps/pdftools/tests/ui.py +++ b/apps/pdftools/tests/ui.py @@ -17,8 +17,10 @@ what the light pass left): Home and its status line against the storage's own bytes (files.status), "Open a PDF from this device" refused, reading and thumbnails, Read mode (entered from the bar, - a page turned and the zoom changed, left by Exit read mode or the - corner control on that page at that zoom), find from the top bar + which puts the window in full screen too, a page turned and the + zoom changed, left by Exit read mode or the corner control on that + page at that zoom, with the window back; then left by Esc, the + shell's key in full screen), find from the top bar alone (the left panel given back), a highlight and its reply dated on this machine's clock, a rotation and its undo, combine with a page range, initials placed by Fill & Sign, a text edit with @@ -96,11 +98,16 @@ def port_free(port): def octosense_running(): - """A running OctoSense: the shell's own binary, or the packaged app.""" + """A running OctoSense: any executable named octosense* (a build, or a + copy such as a demo's octosense--), or the packaged app.""" + result = subprocess.run(["ps", "-axo", "pid=,comm="], capture_output=True, text=True) found = [] - for pattern in (["-x", "octosense"], ["-f", "OctoSense.app/Contents/MacOS"]): - result = subprocess.run(["pgrep", *pattern], capture_output=True, text=True) - found += result.stdout.split() + for line in result.stdout.splitlines(): + pid, _, command = line.strip().partition(" ") + name = os.path.basename(command.strip()) + if (name.lower().startswith("octosense") and name != "octosense-update-helper") \ + or "OctoSense.app/Contents/MacOS" in command: + found.append(pid) return found @@ -868,14 +875,36 @@ def corner_exit(app): app.click(bx + bw / 2, by + bh / 2) +def window_rect(app): + """The whole window, in points: what a full-screen window covers.""" + w, h = app.size() + return [0, 0, w, h] + + +def same_rect(a, b): + return len(a) == len(b) and all(abs(x - y) < 1.5 for x, y in zip(a, b)) + + +def full_screen(app): + """Wait until the app covers the whole window: Read mode's tap asked the + shell for full screen (window.fullscreen). A hidden shell never asks the + host window itself, so the window is the screen here.""" + wait_until(lambda: same_rect(app.area(), window_rect(app)), + f"the app did not go full screen: {app.area()} in {window_rect(app)}") + return app.area() + + def read_mode(app, out, p, dark): """Read mode (the user: "there is no full screen read mode?"): entered - from the floating bar's last button, the pages alone; a page turned and - the zoom changed in it; left from the bar's Exit read mode (light) or the - corner control (dark), on that page at that zoom.""" + from the floating bar's last button, the pages alone and, in a shell, the + window in full screen; a page turned and the zoom changed in it; left + from the bar's Exit read mode (light) or the corner control (dark), on + that page at that zoom, with the window back where it was.""" + windowed = app.area() page = next(w["t"] for w in app.widgets() if re.fullmatch(r"\d+ / \d+", w.get("t") or "")) pill_end(app) app.find("Exit read mode", "Button") + full = full_screen(app) if isinstance(app, Shell) else None app.gone("Search in document") assert labels(app, "Outline") == 0 and labels(app, "Comments") == 0, "the rail shows in Read mode" assert not [w for w in app.widgets() if w.get("t") in ("Saved", "Edited")], "the status line shows in Read mode" @@ -898,16 +927,44 @@ def read_mode(app, out, p, dark): app.tap("Exit read mode", "Button") app.find("Search in document", "TextInput") # the bars are back app.gone("Exit read mode") + if full is not None: + wait_until(lambda: same_rect(app.area(), windowed), f"the window did not come back: {app.area()}, was {windowed}") app.find(turned, "Label") assert zoom(app) == zoomed, f"Read mode left at {zoomed}, the document shows {zoom(app)}" app.images(1) app.grab(out, f"{p}02e-read-mode-left") RECEIPT.setdefault("read_mode", []).append({"pass": "dark" if dark else "light", "entered_on": page, "left_on": turned, - "zoom": [before, zoomed], "left_by": "corner control" if dark else "Exit read mode"}) + "zoom": [before, zoomed], "left_by": "corner control" if dark else "Exit read mode", + "window": windowed, "full_screen": full, "after": app.area()}) zoom_button(app, -1) # the zoom the rest of the journey reads at wait_until(lambda: zoom(app) == before, "the zoom did not go back") +def read_mode_escape(app, out, p): + """Esc, the shell's key while Read mode has the window in full screen + (a script gets no keys): the shell ends the full screen, tells the app + (on_app_fullscreen), Read mode ends with it on the same page at the same + zoom, and the window is back.""" + windowed = app.area() + page = next(w["t"] for w in app.widgets() if re.fullmatch(r"\d+ / \d+", w.get("t") or "")) + level = zoom(app) + pill_end(app) + app.find("Exit read mode", "Button") + full = full_screen(app) + app.images(1) + app.grab(out, f"{p}02f-read-mode-full-screen") + app.key("Escape") + app.find("Search in document", "TextInput") # Read mode is over + app.gone("Exit read mode") + wait_until(lambda: same_rect(app.area(), windowed), f"the window did not come back after Esc: {app.area()}, was {windowed}") + app.find(page, "Label") + assert zoom(app) == level, f"Esc left Read mode at {zoom(app)}, it was entered at {level}" + app.images(1) + app.grab(out, f"{p}02g-read-mode-escape") + RECEIPT.setdefault("read_mode_escape", []).append({"window": windowed, "full_screen": full, "after_escape": app.area(), + "page": page, "zoom": level}) + + NO_IMPORT = "This OctoSense can't open files from the device for PDF Tools." @@ -973,6 +1030,8 @@ def shell_journey(app, out, dark): app.images(2) app.grab(out, f"{p}02b-page-2") read_mode(app, out, p, dark) + if not dark: + read_mode_escape(app, out, p) # One search, in the top bar: the rail offers Pages, Outline and Comments. assert rail_labels(app) == ["Pages", "Outline", "Comments"], f"the rail: {rail_labels(app)}"