GNOME's Wayland compositor draws no window frames (it offers no
zxdg_decoration_manager_v1), so winit drew its own fallback title bar,
which ignores GNOME's theme, dark mode and button layout. PdfCraft now
asks the compositor at launch (apps/pdfcraft/src/decorations.rs) and,
where it draws no frames, turns winit's frame off and makes the tab strip
the header bar, as in GNOME's own apps
(crates/ui-egui/src/window_controls.rs):
- Minimize, maximize/restore and close in Adwaita's style (round buttons,
symbolic glyphs drawn in code, no new assets), in the order and on the
side named by org.gnome.desktop.wm.preferences button-layout, read
through gsettings; Fedora's default `appmenu:close` shows only close.
Hidden in full screen.
- Resize zones along the window's edges and L-shaped corners, above the
rest of the UI so a press there resizes instead of reaching a
scrollbar; a hairline outlines the window while it isn't maximized.
- Preferences > Use system title bar (shown only where the app draws the
frame) gives the window back winit's frame at once, without a restart
(part of #1001). Named as in VectorCraft and PhotoCraft, but GNOME draws
no title bars for apps, so on GNOME this is winit's basic frame, not
GNOME's own; the tooltip says so. Saved as `system_title_bar`;
`system-title-bar` on the control channel.
- PDFCRAFT_TITLEBAR=app|system overrides the detection.
KDE, Sway, Hyprland, COSMIC and X11 offer server-side decorations and
keep the frame their desktop draws; macOS and Windows are unchanged.
Not done: right-clicking the header bar doesn't open GNOME's window menu
(egui has no command for it), the window has no shadow or rounded
corners, the resize zones lie just inside the window rather than outside
it, and Windows still uses its system frame (the rest of #1001).
The new labels (Minimize, Maximize, Restore, Use system title bar and its
tooltip) were added to all 21 translation catalogs. These translations
have not been checked by anyone fluent in those languages and should be
reviewed. The catalogs' hashes in ATTRIBUTION.toml are updated.
Made with Claude Code (Claude Opus 5.5, medium effort). It passes the
workspace quality gates (fmt, clippy, cargo test --workspace, xtask
assets) and was checked by hand on Fedora 44 GNOME (light and dark,
maximized and not, the preference toggled live), but it has not been
exhaustively regression tested.
Co-authored-by: bmh129 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Some programs start the PDF reader with the path unquoted
(`pdfcraft.exe P:\Archive\00042A26 Contract.PDF`), so Windows splits it
into `P:\Archive\00042A26` and `Contract.PDF` and neither opens
("Couldn't read 00042A26: os error 2"). Acrobat accepts this. Rejoin
consecutive arguments when one names no file but it and the ones after it,
joined with a space, do (longest run first); everything else is kept as
given. Done before the single-instance hand-off, so files sent to a running
PdfCraft are rejoined too.
Co-authored-by: magdale76 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
wgpu validates indirect draw and dispatch arguments with a compute shader it
compiles when the device is created. Metal on an Intel Iris Pro (MacBookPro11,3,
macOS 12.7.6) fails to compile it ("Compiler encountered an internal error" in
MTLCompilerService), the device is lost, and the window falls back to OpenGL.
PdfCraft draws and dispatches nothing indirectly, so leave the check out of the
instance flags. WGPU_VALIDATION_INDIRECT_CALL=1 turns it back on. VectorCraft
does the same (storytold/vectorcraft#651).
Co-authored-by: Claude Opus 5.5 <[email protected]>
A build compiled with PDFCRAFT_BUILD_LABEL=<label> shows "0.7.1 (<label>)"
in `pdfcraft --version`, `pdfcraft-cli --version`, Help > About and the
first log line (pdfcraft_engine::build_info). The version itself is
unchanged, so the update check, the MCP server info and the User-Agent
still carry the release version.
packaging/linux/package.sh puts the label in the file names
(pdfcraft-0.7.1-<label>-linux-x86_64.rpm) and makes the .rpm/.deb release
`1.<label>` (nfpm `release: ${RELEASE}`, 1 when unlabelled). rpm sorts
0.7.1-1.<label> after 0.7.1-1 and before 0.7.2-1, so a locally built
package upgrades over the installed release and the next release replaces
it. packaging/env.sh accepts only letters, digits and inner dots, which
are valid in both rpm and deb release fields.
Unlabelled builds are unchanged.
Made with Claude Code (Claude Opus 5.5, medium effort). It passes the
workspace quality gates (fmt, clippy, cargo test --workspace, xtask assets)
and was checked by hand on Fedora 44, but it has not been exhaustively
regression tested.
Co-authored-by: bmh129 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
`pdfcraft-cli audit` skipped option validation, so a misspelled
`--passwrod` fell through to a file-read error instead of naming the
bad option. Audit now runs `validate_options` first, like `info`, and
the options test covers it.
Co-authored-by: umesh429 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* test(cli): reject unknown MCP options
* Tests: wait longer for background text and rendering in viewer tests (FreeBSD CI flake); rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* M3: follow Windows' Animation effects setting; Smooth scrolling preference
With Settings > Accessibility > Visual effects > Animation effects turned
off, the wheel still eased: egui spreads a coarse wheel step over about a
quarter of a second, for scrolling and for Ctrl-wheel zooming, and has no
switch for it.
- crates/ui-egui/src/motion.rs reads each frame's wheel input the way egui
does, minus the easing, and hands it to the scroll areas and the views
(zoom_delta). A test sums what egui eases and compares it with what is
read at once, and fails if egui stops easing.
- With the system asking for no animations, egui's own animations take no
time and the progress card's bar steps without its sweep.
- The desktop app asks WinRT's UISettings.AnimationsEnabled at launch and
every two seconds of use, so a change applies without a restart.
- Preferences has Smooth scrolling: Use system setting (default), On, Off.
On or Off decides the wheel's scrolling and zooming, and scrolling to a
place, whatever the system says. Saved, settable as smooth_scrolling on
the control channel, translated in all 20 catalogs.
How far a notch scrolls or zooms is unchanged. macOS, Linux and the
browser aren't asked for their setting yet; Off works everywhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Credit Jeremy Yao by name in About (contributors/people.toml)
My own entry: real name and display name "Jeremy Yao".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M3: rename Smooth scrolling to Animation effects; it decides every animation
The preference also governs Ctrl-wheel zooming, so "Smooth scrolling"
described only part of it. As "Animation effects" (Use system setting, On,
Off) it now decides every animation in the window, the way the Windows
setting of the same name does:
- the wheel's scrolling and zooming, and scrolling to a place (as before);
- egui's own animations, the progress card's sweep, and the chrome's hover
and press washes (new on main, with durations of their own), which used
to follow the system alone or not at all.
Saved and set on the control channel as animation_effects (system, on,
off); the unreleased smooth_scrolling name is gone. The three labels are
retranslated in all 20 catalogs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Tests: the app's two real-GPU tests take turns (FreeBSD SIGSEGV)
FreeBSD CI died with SIGSEGV in the pdfcraft binary's tests on this branch
(d4b928f1). The log shows a_gpu_error_while_a_window_is_set_up... (started
first, still running) and pipeline_creation_error... (started shortly
before) both in flight when the process died: two test threads inside the
graphics libraries at once. Nothing else still running was native code.
Both now take one lock for the whole test, as the UI tests already do for
the same reason (crates/ui-egui/tests/view.rs). Each takes about two
seconds with a device, which is why they can overlap.
The FreeBSD job has been failing at the same step with the same exit code
on main (84a75eae, 4a1e205f, 3d2bdfe7: every completed run since #1042
added the second real-GPU test) and on unrelated PRs. Their logs weren't
available to confirm it is the same crash.
Not reproduced locally (no FreeBSD machine here); the next FreeBSD run is
the check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* fix(x11): prefer server DPI over panel physical DPI in winit
Without Xft DPI, use the X server screen dimensions before the output physical density. Xorg can run a dense panel at 96 DPI; EDID sizes alone make only winit apps appear zoomed.
Supersedes #908: preserves Xft.dpi priority, configured HiDPI screens, and WINIT_X11_SCALE_FACTOR override (including randr). Falls back to RandR output density when server dimensions are missing.
Adds default_dpi_factor() with 4 unit tests plus vendored-copy guard test in apps/pdfcraft/tests/winit_x11_dpi.rs.
* vendor/README: record the winit X11 DPI patch as (11)
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Heavy pages: cull off-tile images, cache decodes, preview backdrops, calmer queues
A large CAD sheet (page 24 of the benchmark file: ~5050 pt wide, 2104
images, 9 MB of content) cost a full page interpret per tile: 4.3 s for a
1024 px tile at 1000%, 10.5 s for the whole page, and a storm of renders on
every pan frame. Tiles now cost 0.6 s, the sheet is covered by a handful of
large tiles, and gestures no longer queue work faster than workers finish it.
- Vendored hayro patch (10): skip images whose stream-size box misses the
canvas before decoding them, so a tile no longer decodes, resamples and
rasterizes every image on its page. Pixel-identical; the skip regression
test now covers images too.
- Vendored hayro-interpret patch (23): decoded image pixels shared across
renders through the interpreter cache, keyed by stream and decode target
size within a 64 MiB budget.
- Preview renders (hayro patch 11, RenderRequest::preview through to the
device): backdrops, thumbnails, grid cells and signature-drag backgrounds
skip images under 16 device px. Tiles, whole pages, print and export always
draw everything. Previews may differ by those pixels; finals don't.
- Viewer: zoom/pan settle holds apply only to pages whose raster proved slow
(slow_pages, 500 ms), so fast pages render with no added latency; tile sides
grow with zoom and page size (giant sheets need ~8 tiles, not ~28), queued
center-first and keyed by exact coordinates; Edit-text outlines capped.
- vendor/README.md records the new patches with their tests.
Co-Authored-By: Muse Spark <[email protected]>
* Decoded-image cache: key every decode input, survive a poisoned lock
The key was the dictionary's bytes and the target size, so two images with byte-identical
dictionaries (uncompressed, one size) shared pixels, and an image drawn under a transfer
function reused pixels decoded without it. DecodedImageKey now holds the dictionary's place
in the document (its data follows it), its bytes and object, mask/stencil/raster output,
interpolation, the target size and preview or final quality; images under a transfer
function and inline images (named colour spaces, dictionaries in content streams) are not
cached, and an image over the budget is never kept. A poisoned lock decodes uncached
instead of panicking. Soft masks: the per-renderer cache can't carry a preview mask into a
final render (documented). Regression tests for identical dictionaries, preview then final
through one cache, and transfer functions.
---------
Co-authored-by: Muse Spark <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Issue #432: global Units preference (mm, cm, in, pt), defaulting to the system region
Preferences ▸ Documents and view ▸ Units sets the unit for page sizes
(Document Properties, Print), Header & Footer margins, the start of Set
Page Boxes and the Measure tool on pages without a viewport scale. On
first start it follows the system region: AppleMeasurementUnits on macOS,
Region ▸ Measurement system (iMeasure) on Windows, then LC_ALL /
LC_MEASUREMENT / LANG; inches for US, LR and MM, millimetres elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Issue #432: macOS falls back to AppleLocale when the measurement system was never set
AppleMeasurementUnits only exists in the global domain once someone changed
Measurement system in System Settings, and apps started from the Finder get no
LANG, so a German Mac with untouched settings landed on inches. The region of
AppleLocale (always present) decides next; AppleMeasurementUnits still wins.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu
* Issue #432: Units labels in the Arabic and Italian catalogs new on main
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu
* Issue #432: the Print dialog's PDF page size follows the Units preference
Main's new "PDF page: {w} × {h} in" label was always in inches. It now uses
the preferred unit like the sheet size next to it; each catalog keeps its
wording with the size in place of the inch values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Tristan von Kries <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* fix(theme): complete Linux System appearance tracking
Follow up on #881 for #446 with the standard numeric appearance portal setting
and event-driven updates that wake idle windows. Keep manual modes independent,
cache their desktop updates without repainting, and cancel the worker on close.
Retain inherited native window appearance and populate both egui style branches
with the chosen palette. Preserve saved System, the Light default and unknown
source fallback, and avoid desktop session access in headless UI contexts.
Assisted-by: Codex (gpt-6.1-sol)
* Desktop theme: keep the GNOME colour-scheme fallback XFCE needs
The portal tracker read only org.freedesktop.appearance, which XFCE's portal
doesn't answer (it proxies the GSettings schemas). It now also reads and
subscribes to org.gnome.desktop.interface color-scheme and uses it when the
freedesktop setting states no preference, as main's reader did. KDE answers
only the freedesktop one, which wins when it states a preference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Rework faux-redaction audit per maintainer review
- Paint-order aware: a rectangle counts as a cover only when painted
strictly after the text (positions from the joined content stream).
- Colour-aware: WCAG contrast between text and cover must be below 3.0,
so page backgrounds, table shading and highlights are not flagged.
- Fail closed: audit_redactions returns Err on undecodable content
streams (decoded_strict) and malformed /Annots; the operator bound is
an error, not a silent truncation.
- Graphics state carries across a page's content streams (one joined
walk); form XObjects still isolate their state at Do.
- Audit-only: the sanitize-for-sharing half is dropped (upstream #162).
- Parity entry marked partial, not shipped.
* Audit: register its layer, add a README, fail closed past 32 nested forms, document what it doesn't check
---------
Co-authored-by: Erick Barraza <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Viewer: touchpad scrolling tracks the finger 1:1 (#759)
A precision touchpad sends pixel-precise `MouseWheel` events. On Windows
they arrive with no `TouchPhase::Start`, so egui never takes its
no-smoothing "in touch" path and eases any delta of 8 px or more over
several frames, leaving the page a frame behind the finger.
The page view now applies precise (Point-unit) wheel input itself, 1:1,
through the same un-eased scroll path as keyboard and middle-button
auto-scroll, and suppresses egui's eased copy for a short window so its
internal easing tail neither doubles nor trails the motion. A coarse
mouse wheel (Line/Page units) keeps egui's smoothing, which is pleasant
for chunky notches, and single-page paging still owns the vertical axis.
Regression test: one 200 px precise swipe scrolls the full 200 px at
once (an eased path would ramp there over ~0.1 s) and leaves no drift.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Viewer: Continuous scrolling is an independent checkbox over the base layout (#752)
Page display matches Acrobat: the base layout (Single page or Two-page
view) is a radio choice, and Continuous scrolling is a separate checkbox,
so every combination is reachable. This also adds the previously missing
"Two-page view, one spread at a time" mode.
`PageLayout` now has just `Single` and `TwoUp`; a separate `continuous`
flag on the view (and on the default view preferences) decides whether it
scrolls. A single `display_token` ("single" | "continuous" | "two-up" |
"two-page") serialises both for settings and the control channel, keeping
the earlier `continuous`/`single`/`two-up` names working and adding
`two-page` for the new mode. The PDF's own page-layout request now maps
to the right (layout, continuous) pair, including plain TwoUp.
- View ▸ Page display and the rail menu show two radios plus a Continuous
scrolling checkbox; Preferences gains a default Continuous checkbox.
- `view.continuous` toggles the checkbox; `view.layout.single` /
`view.layout.two_up` switch the base layout without touching it. The
former `view.layout.continuous` command is gone (its state is Single +
continuous); the `layout`/`default-layout` options and a new
`continuous` option cover scripting.
- Not scrolling, the wheel and the arrow/Page keys turn whole spreads in
Two-page view, and only the current page or spread is laid out.
Tests: new `notches_turn_two_page_spreads`,
`two_page_view_shows_one_spread_at_a_time_until_continuous`, and updated
layout/command/persistence tests; the `view()` helper takes `continuous`.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Parity: the continuous-scrolling feature points at view.continuous
---------
Co-authored-by: Justin John <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
System Settings > Desktop & Dock > "Close windows when quitting an application"
is the system's own version of Preferences > "Reopen the files that were open
when PdfCraft last closed". Read it once at launch (defaults read -g, 500 ms
guard, as text_scale.rs does for gsettings) and use it as the preference's
starting value, before the saved preferences are restored. A choice made in
Preferences is persisted and still wins; other systems keep starting off.
Co-authored-by: Tristan von Kries <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
* M13: report unavailable symbolic-link fixtures in hostile-argument tests
Only include the planted link escape when symlink creation succeeds. On
non-elevated Windows the fixture can fail with OS error 1314; exercising
its nonexistent path otherwise produces false confinement leaks. Keep
all remaining traversal and absolute-path cases and report the skip.
Co-authored-by: Codex (GPT-6) <[email protected]>
* M3: return renderer initialization errors to the OpenGL startup fallback
Guard renderer creation with the existing scoped GPU-error handler and
return CreateRenderer instead of invoking wgpu's default panic handler.
This lets Auto startup reach its existing OpenGL retry before app creation.
Add a real-device invalid/valid pipeline regression and Windows startup
reproduction notes. The host-specific DX12 shader compiler error itself
is not fixed; explicit renderer choices and the non-winit path are unchanged.
Co-authored-by: Codex (GPT-6) <[email protected]>
* Leave the symlink-test fix to #949, which covers the same ground
---------
Co-authored-by: Codex (GPT-6) <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Linux: StartupWMClass is the window's app id
The desktop entry said StartupWMClass=pdfcraft, but the window carries the
app id ai.storyteller.pdfcraft (APP_ID) on Wayland and under X11 alike, so
nothing that matches a window to its launcher by that key ever matched it:
on X11 the launch's startup notification can't end when the window maps.
VectorCraft, GridCraft and WordCraft already use the app id.
A test reads the entry and compares it with APP_ID.
* Contributors: add atoslins (Atos Lins)
---------
Co-authored-by: Brandon Thomas <[email protected]>
On a hybrid laptop whose Wayland compositor runs on the discrete GPU
(Hyprland with AQ_DRM_DEVICES naming the NVIDIA card first), the built-in
panel still hangs off the integrated GPU, so the panel rule (#8) picked it,
and the window stayed empty: the compositor took the frames and showed
nothing, with no error anywhere.
Which GPU drives a display stands in for which GPU the compositor draws
with. Under Wayland the compositor says so itself: the main_device of its
linux-dmabuf feedback (version 4), the device it imports client buffers on,
which Mesa's Vulkan device-select layer reads for the same purpose.
compositor_gpu.rs asks for it on a short-lived connection, maps the dev_t
to PCI ids through /sys/dev/char, and pick_adapter ranks that adapter
first. No answer (X11, an older compositor, a GPU no adapter matches)
leaves the existing rules in charge, and WGPU_POWER_PREF still overrides.
wayland-client and wayland-protocols are already in the tree through winit;
they become direct Linux-only dependencies.
* M10: Windows printing prints at actual size, off the UI thread (follow-up to #847)
- Sheets print at 100% on the driver's paper closest to their size, landscape
when wide, instead of being shrunk into .NET's default 1-inch margins
(about 76% on Letter) and never rotated. Each sheet is capped at ~64 MP so a
poster-sized sheet can't exhaust memory.
- PowerShell runs with CREATE_NO_WINDOW: the release app has no console, so
Windows opened one over the app for the whole job.
- The Print dialog lists printers in the background (the desktop app's
printer_source) and spools jobs on a worker thread, so Ctrl+P and long jobs
no longer freeze the UI. A list arriving late preselects the default only if
the user hasn't picked a printer.
- Parity evidence named a test that #847 renamed; xtask parity passes again.
The raster-only limit is now stated in the parity notes and README.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M10: refresh catalog hashes in ATTRIBUTION.toml for the new print strings
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* M14: give Windows PDF files a dedicated document icon
Embed the document icon as resource 2 through the existing Windows resource
build and select it in both PDF association registrations. Keep the asset
reproducible and carry its required licence notices in Windows packages.
Formatting, strict Clippy, 1,842 existing workspace tests, all 30 WASM crate
checks, assets, layering and parity pass. The Windows x64 release package
checks pass, and the contributor confirmed the icon in Explorer.
Co-authored-by: Codex <[email protected]>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Codex <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M14: interface size follows the system text scale
Preferences ▸ Interface size scales the whole interface (75–250 %, setting
`ui_scale`, control option `ui-scale`). Auto, the default, follows the
desktop's text scaling: on Linux and the BSDs the desktop app reads GNOME's
`text-scaling-factor` once at launch (through `gsettings`, with a 500 ms
limit; anything unusable counts as 100 %). With Large Text (1.25) on a 1×
display, the interface no longer draws 13 px labels that show their pixels
next to the system's text.
The size is egui's zoom factor, so `pixels_per_point` grows with it and page
rasters stay one texel per screen pixel (`canvas::render_scale`). It is
applied only when it changes, so a zoom set elsewhere (the test harness's
`pixels_per_point`) isn't undone every frame. egui's own ⌘+/⌘−/⌘0 interface
zoom is turned off so those keys stay page zoom.
Tests and headless screenshots keep 100 %: only the desktop app sets
`system_text_scale`. New labels are translated in every catalog, and the
catalogs' hashes in ATTRIBUTION.toml are updated.
* M14: offer a 50 % interface size
On a display the system reports at 200 %, the smallest size the menu offered
gave a 150 % interface (#456). 50 % brings it back to 100 %; settings and the
control channel already accepted it.
---------
Co-authored-by: Brandon Thomas <[email protected]>
Combine files shows each file as a card (the default; Grid/List kept in
the settings, --combine-view): the first page it adds, stacked when it
adds more, its name shortened in the middle. Cards select (click, Ctrl,
Shift, arrows by card and by row), drag to reorder (one undo step; the
grid scrolls at its edges) and share the list's toolbar, selection and
history. Locked, unreadable, bad-range and unmeasurable files say why.
Thumbnails render off the UI thread, only for cards in view: at most
three render threads at once, counted until they exit (no watchdog
replacements), a 32 MB texture budget, keys that never show another
page, password or file, and one heavy step per frame on the web.
Cards are sized from 60% to 200% (toolbar slider and buttons, Ctrl
+/-/0, Ctrl+wheel or pinch over the grid, kept in the settings,
--combine-zoom), keeping the card under the pointer in place. Hovering
a card shows its actions over the top of its page: remove that file,
and a magnifier that shows it large, flipping through the pages it
adds; the same on a right click, and Space by keyboard. The toolbar
wraps between groups at narrow widths.
RenderPool gains retire(), without_replacements() and threads() so a
caller can bound the render threads that exist at once.
Co-authored-by: Andrew Taylor <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Single instance: enable tab handoff in Windows portable mode (#889)
* Single instance: a portable copy's socket stays in %LOCALAPPDATA%, under its own name
(cherry picked from commit c7db1e80a374e80a11504efefbaac680dfa5e872)
---------
Co-authored-by: Brandon Thomas <[email protected]>
A page that frames PdfCraft on the same origin can now pass it PDFs and
receive saves through postMessage, instead of Save becoming a browser
download. Built for a Nextcloud integration, but nothing in it is
host-specific.
- ui-egui: HostSaveFn/HostDirtyFn hooks (`host_save`, `host_dirty`),
None by default. On wasm, Save hands the bytes to `host_save` when
set; `host_dirty` hears each frame whether any tab has unsaved work.
- pdfcraft-web: `host::Bridge`, enabled only by `?host=parent` inside a
frame. Messages: pdfcraft:ready / open / save / dirty / failed; only
the parent window on our own origin is heard or answered.
`?author=` names comment authors unless the user chose a name.
- Test: host_dirty_hears_when_unsaved_work_appears_and_goes.
Without `?host=parent` nothing changes.
Claude-Session: https://claude.ai/code/session_01G6GtUCaPiCMEQCN4QcdQnp
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Windows: open files in the running PdfCraft instead of a new window each time (#282, #317)
Outlook attachments, Explorer double-clicks and Open With start pdfcraft.exe with the file,
so every file got its own process and window. Now the first PdfCraft listens on a Unix domain
socket in %LOCALAPPDATA%\PdfCraft (or a portable copy's data folder), one per Windows session.
A later launch that only names files sends their absolute paths, waits for "ok" and exits; the
running app opens them as tabs through the existing OsEvent::Open path (the one macOS Apple
events use) and comes to the front.
Anything else keeps today's behaviour: --new-window, any other option, a launch without files,
or any failure to hand over within 5 seconds opens a window of its own. Other platforms are
unchanged.
The socket lives in the user's profile, so other accounts can't create, replace or reach it.
uds_windows (already in the lockfile through zbus) provides it: std has no Unix sockets on
Windows and the workspace forbids unsafe. A socket left by a crash is replaced on the next start.
* Windows: launches started together share one window (#282, #317)
Several files selected in Explorer and opened at once start one process
per file at the same moment. A launch could then find the socket bound
by another launch that was not listening yet: connecting failed, clearing
the socket failed with "Access is denied", and the launch opened a window
of its own.
Launches now take turns through a lock on a file beside the socket, held
only while a launch finds out whether it is the first. A socket that
cannot be cleared because another launch holds it is tried again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016es5tszobbb1yZ7JeozJXL
* Windows: a portable copy doesn't hand files to another instance
Its data folder may be writable by other accounts, so a socket there could leak or inject file
paths. Builds on art804's single-instance work in #602.
Co-authored-by: art804 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: art804 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Help ▸ Check for updates showed a bare "couldn't reach GitHub
(http status: 403)" when the anonymous hourly quota ran out. Read
error statuses in latest_release instead: a 403 now reports the
shared-quota limit with the retry time from X-RateLimit-Reset
("try again in about N minutes"), falling back to "try again later"
when the header is missing or spent. Other statuses keep the old
message. Adds rate_limit_messages_name_the_wait.
Co-authored-by: ShauryaSwarup <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* macOS: IME text committed after the marked text is cleared isn't lost (winit#4650)
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS: the first Apple Korean key in a fresh PdfCraft composes (winit#4744)
On the first key after PdfCraft starts, Apple Korean 2-Set can answer
interpretKeyEvents with insertText and the raw compatibility jamo instead of
setMarkedText. winit sends that key to the app as an ordinary key, and the
next keys compose without it: g k s Space typed ㅎㅏㄴ instead of 한 (Search,
form fields, comments; rust-windowing/winit#3095).
vendor/winit now ports rust-windowing/winit#4744: keyDown interprets that
one key a second time, synchronously, before any KeyboardInput is sent, and
only when every condition holds: Apple Korean, IME allowed and not yet
composing, no pending commit or marked text, no Command/Control/Option, not
a repeat, the key window's first responder, the only callback is one
insertText of that same single jamo, and focus and input source unchanged
afterwards. The decision is ImeStartup (ime_startup.rs, copied with its
seven tests); no timer, no key queue, no text rewriting. It sits on top of
the previous macOS IME change (pending_commit, in_key_down); the retry runs
inside in_key_down. Command maps to ModifiersState::SUPER in winit 0.30
(META on winit master).
Checked with the real input method on macOS 14.7.1 (scratch winit app, fresh
process each time): without the patch g k s Space gave ㅎ as a key, then
ㅏ and ㄴ committed separately, 3 runs out of 3; with it, ㅎ → 하 → 한 → 한
committed, 3 out of 3. All other traces (Japanese, Cangjie, ABC, held
keys, later Korean keys) are unchanged. ImeStartup's tests pass with
rustc --test. A test fails if a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* macOS: IME text committed after the marked text is cleared isn't lost (winit#4650)
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS: Space in Apple Korean with no syllable in progress types one space (winit#4478)
With Apple Korean 2-Set on macOS 14, Space with no syllable in progress
(a second space, or a space after a digit or punctuation) makes AppKit
call setMarkedText(" "), insertText(" "), then setMarkedText(""). winit
reports the insertText as Ime::Commit(" "), but the trailing empty
setMarkedText returns its state to the ground state, so keyDown also sends
the key as KeyboardInput with text " ". egui inserts both, and PdfCraft's
text fields got two spaces (the double space of alacritty/alacritty#8079).
vendor/winit now records that the key being handled committed IME text and
then doesn't also send it as KeyboardInput, unless AppKit forwards it as a
command through doCommandBySelector (Enter after a syllable still commits
the syllable and sends Enter). keyDown consumes the record on every key,
so keys typed after IME is disallowed are unaffected.
rust-windowing/winit#4478 aims at the same double space, but its changes
(clearing the marked text on commit, forwarding a later insertText, and
dropping doCommandBySelector's guard) don't reach this call order, and the
key loss it also fixes is already fixed by the previous change
(pending_commit). This is the narrow form for the call order observed
here; it should go upstream with #4478.
Checked with the real input methods on macOS 14.7.1 (scratch winit app, on top
of the previous change): the duplicate KeyboardInput Space is gone and
nothing else changes (Korean syllables, 한+Enter, 한+5, Japanese, Cangjie,
ABC, held keys, keys after IME is disallowed). A test fails if a
re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Keep #678's emoji-picker branch: view.rs is main's plus this PR's Space fix (54b3d3c)
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* macOS: IME text committed after the marked text is cleared isn't lost (winit#4650)
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS: emoji from the emoji picker reach text fields (winit#4749)
The macOS emoji picker (Fn/Globe-E, Control-Command-Space) inserts its text
with insertText:replacementRange: outside any key press and without a
composition. winit 0.30.13 reports insertText only as part of a key press or
a composition, so an emoji picked into Search, a form field or a comment
was dropped.
vendor/winit now reports text inserted outside a key press, with IME
allowed and no composition, as Ime::Commit, as rust-windowing/winit#4749
(the 0.30 backport of #4748) does. Builds on the pending_commit and
in_key_down state of the previous macOS IME change.
#4748's selectedRange change, which lets dictation start, is left out:
checked on macOS 14.7.1, a valid selection switches on press-and-hold for
every held key in a winit window, so holding e kept sending e key events
under an accent menu, and the accent picked with 1 arrived as a separate
commit after "eeee…1". Without it, held keys repeat as before.
Checked with a scratch winit app on macOS 14.7.1: an insertText outside a key
press now arrives as Ime::Commit; held keys, ABC, Japanese, Cangjie and
Korean typing give the same events as before. A test fails if a re-vendored
winit loses the patch or gains the selectedRange change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
egui disallows IME when a text field loses focus, for example when the user
clicks the page while composing. winit 0.30.13 then clears its own marked
text but never tells the input method, which keeps the composition and
continues it once IME is allowed again. Checked with the real input methods
on macOS 14.7.1: Korean 하, IME off and on, then r k gave 학, then 하 and 가
(하 committed a second time if the app already kept it); Japanese か, IME
off and on, then s a gave 傘.
vendor/winit now calls NSTextInputContext::discardMarkedText when IME is
disallowed with marked text present, before its existing reset, as
rust-windowing/winit#4745 does on winit master. With the patch the same
sequences give 가 and さ. AppKit may call unmarkText from
discardMarkedText while IME is still enabled, so its Preedit("") comes
before Disabled; the RefCell borrow in the check ends before the call.
Everything else in the scratch app's traces (ABC, Japanese, Cangjie,
Korean) is unchanged. A test fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
egui calls Window::set_ime_allowed(true) when a text field gains focus and
set_ime_allowed(false) when it loses it. On X11, winit 0.30.13 handles each
change by destroying the window's X input context and creating a new one, and
leaves focusing the new context to the next FocusIn. PdfCraft's window keeps
the focus while the user moves between Search, form fields and the canvas, so
no FocusIn comes and the new context stays unfocused: the input method
(IBus, fcitx5) doesn't follow the field until the window loses and regains
focus.
vendor/winit's Ime::set_ime_allowed now returns create_context's result, and
the event processor, after draining the queued IME requests, focuses the last
context replaced for the active window, once, as
rust-windowing/winit#4727 does on winit master. No public API changes. A
test fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
eframe creates PdfCraft's window hidden, restores the saved state
(app.ron, persist_window) and shows the window after the first frame is
drawn. For a window closed maximized, winit 0.30.13 applies the maximized
state with ShowWindow(SW_MAXIMIZE), which shows a hidden window: PdfCraft
appeared unpainted (white), was hidden again, then shown with SW_SHOW and
SW_MAXIMIZE once more. rust-windowing/winit#4586 also shows the window's
focus state going wrong on this path.
vendor/winit now skips the maximize while the window is hidden and shows a
hidden window that is maximized with one SW_MAXIMIZE, as
rust-windowing/winit#4587 (approved upstream) does on winit master. That is
the later, focus-safe form of #4575; #4575's other change (re-sending
SW_MINIMIZE on every flag change while minimized) is not taken. Transitions
between two visible states make the same ShowWindow calls as before. A test
fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
When a composition has no target clause, winit 0.30.13 reads the cursor with
ImmGetCompositionStringW(GCS_CURSORPOS) without checking that the
WM_IME_COMPOSITION lParam has GCS_CURSORPOS set. The Microsoft Korean IME
never sets it (lParam 0x6018), so the read gives 0 and winit reports
Ime::Preedit("한", Some((0, 0))). egui 0.36.2 recognises that exact report on
Windows and drops the range, so PdfCraft draws no caret while Korean is being
composed.
vendor/winit now reads GCS_CURSORPOS only when the flag says the IME set it
and otherwise puts the cursor at the end of the composition, as
rust-windowing/winit#4746 does on winit master (Firefox and SDL do the same).
IMEs that report a cursor or a target clause (Japanese, Chinese) are
unchanged. A test fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
winit 0.30.13 destroys a seat's zwp_text_input_v3 in
SeatHandler::remove_capability, whichever capability went away (a touch
screen or tablet unplugged, a remote-desktop session ending), and only
new_capability recreates it. A seat that loses a capability without gaining
one leaves PdfCraft without a text input: IME (fcitx5, IBus) stops working
until PdfCraft is restarted.
The text input belongs to the seat, so vendor/winit now destroys it in
remove_seat only, as rust-windowing/winit#4747 does on winit master. A test
fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M3: harden render and CLI failure surfaces
Make content truncation observable, reject unknown CLI options, expose deterministic render work counters, and scope render failures to exact requests. Refuse silently reduced whole-page export rasters while preserving tiled rendering, and add heavy-vector and CLI regression coverage.
Co-authored-by: Codex <[email protected]>
* Render: remember a failed render by page and kind again
A key with scale and tag made every zoom level and tile a new key: a
pathological page tied up a worker for the watchdog limit per request
(exhausting worker replacements) and was re-parsed each time. The
repeated-parsing check asks for a new generation and scale again.
* Export: pages too large at the chosen dpi export at the most the renderer allows, and say so
#669 made every Exporter refuse oversized whole-page renders, which
reached File > Export to image, doc_export_* and OCR: at 600 dpi any page
side over 13.65 in (Legal, A3, Tabloid) failed instead of exporting. The
exporter clamps again; Exporter::dpi_used reports the resolution a page
is drawn at, which the Export dialog's message and doc_export_png's
lower_dpi list mention. A source with reject_oversize still refuses
(CLI render keeps its strict config).
* doc_export_images: describe lower_dpi
* Render: ContentTruncated is reported once per page; the hayro-interpret patch is recorded
Nested forms, Type 3 glyphs and patterns are interpreted again and each
reported the exhausted budget; pdfcraft-render now keeps one. The warning
is part of hayro-interpret patch (18) (the page content budget): marked
in the code and described in vendor/README.md.
* CLI render: print render warnings (skipped content) to stderr
* CLI: the option check refuses unknown options only; commands report missing values themselves
text's own --page parsing (and its test on main) says what a page value
must be; the generic "needs a value" pre-empted it.
---------
Co-authored-by: Codex <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Fix#378: draw on the GPU that drives the primary display on Windows
The low-power preference picked the AMD Ryzen integrated GPU on a desktop
whose monitors all hang off an NVIDIA card. Presenting from the headless
iGPU to a window on another GPU took the AMD display driver down, blacking
out every monitor for two minutes before PdfCraft exited.
Windows now gets the display-aware adapter choice Linux already had:
EnumDisplayDevices (through the safe `winsafe` crate, MIT) says which
adapter drives each desktop display and which is primary; `pick_adapter`
matches that to the wgpu adapters by PCI ids. The GPU driving the primary
display wins, as the built-in panel does on Linux (#8). The log names the
GPU chosen, the first question when a window stays black.
Synthetic tests cover the Windows device-id parser and the primary-display
rule; a live test checks the enumeration on the running machine. Verified
on a Ryzen 7000 + RTX 3090 desktop: the app now starts on the 3090.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XBMGuLLMhs1h8orSxcJXER
* 631: the winsafe feature comment says user (and kernel)
---------
Co-authored-by: Claude Fable 5.1 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
pdfcraft-cli.exe had no VERSIONINFO, so Explorer's Details tab,
(Get-Item pdfcraft-cli.exe).VersionInfo and inventory tools showed no
version or publisher for it. A build.rs like the app's embeds one with
winresource (already a build dependency of pdfcraft): the same product
name and copyright, its own description and file names, and the package
version. PDFCRAFT_REQUIRE_WINRES=1, which the Windows packaging already
sets, makes a missing resource compiler an error; otherwise it's a
warning, and other targets are unaffected.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Security: pdfcraft-cli ui refuses a control file another user could plant
`pdfcraft-cli ui --control FILE` sent the token and every command, typed
text included, to whatever port FILE named, without checking who owned it.
The README suggested `/tmp/pc.json`; in a shared /tmp another local user
can create that file first, so the CLI talked to their listener.
The CLI now refuses a symbolic link and anything but a regular file, a file
over 64 KiB, and on Unix a file owned by another user or with any group or
other permission bits (the app writes 600). The owner and mode are read from
the opened file, and its device and inode must match the path's, so a swap
after the check is refused too. std has no geteuid and unsafe is forbidden,
so the effective uid is the owner of a file the CLI creates in the temp
folder and removes. The README now uses a per-user path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Security: stop the app when --control can't be published
A control file that couldn't be written was only logged, and the app ran
on with a listener nobody could reach. The failure can mean another user
already created a file at a shared path to receive the commands, and a
script started with --control would otherwise drive nothing without being
told. The app now exits with the error, before any document opens, so no
work is lost. The README and the app's usage comment say so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CLI: pdfcraft-cli ui refuses a control-file port above 65535
`ui` truncated the control file's port with `as u16`, so 65536 + N reached
port N, and the token and command went to whatever listened there. A port
that doesn't fit is now refused, before connecting.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CLI: learn the effective uid from rustix, not a temp-file probe
Review on #597: the probe made `pdfcraft-cli ui` depend on a writable temp
folder to judge a control file somewhere else, created and removed a file
on every call, and could leave one behind. rustix's `geteuid` is safe and
can't fail, and rustix is already in Cargo.lock; it is a Unix-only
dependency of the CLI, so Windows builds don't compile it.
A control file of your own now works with TMPDIR pointing at a folder that
doesn't exist; the probe refused it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Windows: vendor egui-wgpu 0.36.2 as published (#577)
The crate as released on crates.io, plus egui's LICENSE-APACHE and
LICENSE-MIT from the 0.36.2 tag (the published crate leaves them out).
Not used yet: the next commit patches it and adds it to [patch.crates-io].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Windows: a window larger than the GPU's texture limit can't close PdfCraft (#577)
egui-wgpu 0.36.2 configures a window's surface at the window's own size.
Beyond the device's max_texture_dimension_2d, Surface::configure fails
validation and wgpu's default error handler panics. eframe configures the
surface before the app is created, so no app code can intercept it
(emilk/egui#8361). #578 raised the device's limit to the adapter's; this
removes the crash for windows larger than even that.
vendor/egui-wgpu: `winit::surface_fit` shrinks both sides by one factor so
neither exceeds the limit (the longer side lands exactly on it), and egui
draws at that factor into the surface. On DX12, PdfCraft's Windows default,
the swapchain is stretched over the window (DXGI_SCALING_STRETCH), so the
window stays complete and lined up with the pointer, only softer; wgpu's GL
backend copies it unscaled into a corner instead. It applies wherever a
surface is sized: install, resize (start-up restore, maximise, DPI change,
fullscreen) and a recreated surface, which starts again from the window's
own size. Depth and MSAA textures follow. A surface logs a warning when
it becomes fitted.
Why only an Outlook launch hit it (read from the code, not observed):
winit creates the window at CW_USEDEFAULT, so Windows places it, and a
launcher that hands ShellExecute its own window gets new windows on its
monitor. winit then converts the restored logical size at that monitor's
scale: 3440 points became 8600 pixels on a 250% panel. eframe's clamp
compares points against the largest monitor, so it let 3440 through.
Tests: surface_fit with the issue's 8600 x 3423 and edge sizes (zero,
u32::MAX, a zero limit, the longer side exactly on the limit); egui-wgpu
resolves to vendor/egui-wgpu and the fit stays wired into the paths that
size a surface.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Windows: a GPU that can't set up the window falls back to OpenGL (#519)
On a 2015 Intel GPU with no D3D12 driver, wgpu picked its GL adapter
over WARP and Surface::configure failed (GpuWaitTimeout). wgpu's default
error handler panics, and eframe configures the root surface before the
app is created, so the OpenGL retry from #530 never ran.
Vendored egui-wgpu (PdfCraft patch #519): winit::catch_errors runs a
closure inside wgpu error scopes (validation, out of memory, internal)
and returns the error. Painter::add_surface configures each new window's
surface inside it and returns a failure as WgpuError::ConfigureSurface,
so eframe returns Error::Wgpu and main retries with glow. Later
reconfigures are unchanged.
Tests: a real device returns a wgpu validation error from catch_errors
instead of panicking (red with the scopes removed), and add_surface
keeps the wiring (red with it removed). Docs: vendor/README.md patch 2,
docs/development.md "Renderer fallback" and PDFCRAFT_RENDERER, ROADMAP.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* vendor/egui-wgpu: drop the stray .cargo-ok marker
* Tests: the GPU error-scope test skips on a machine without a GPU adapter
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Windows: vendor egui-wgpu 0.36.2 as published (#577)
The crate as released on crates.io, plus egui's LICENSE-APACHE and
LICENSE-MIT from the 0.36.2 tag (the published crate leaves them out).
Not used yet: the next commit patches it and adds it to [patch.crates-io].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Windows: a window larger than the GPU's texture limit can't close PdfCraft (#577)
egui-wgpu 0.36.2 configures a window's surface at the window's own size.
Beyond the device's max_texture_dimension_2d, Surface::configure fails
validation and wgpu's default error handler panics. eframe configures the
surface before the app is created, so no app code can intercept it
(emilk/egui#8361). #578 raised the device's limit to the adapter's; this
removes the crash for windows larger than even that.
vendor/egui-wgpu: `winit::surface_fit` shrinks both sides by one factor so
neither exceeds the limit (the longer side lands exactly on it), and egui
draws at that factor into the surface. On DX12, PdfCraft's Windows default,
the swapchain is stretched over the window (DXGI_SCALING_STRETCH), so the
window stays complete and lined up with the pointer, only softer; wgpu's GL
backend copies it unscaled into a corner instead. It applies wherever a
surface is sized: install, resize (start-up restore, maximise, DPI change,
fullscreen) and a recreated surface, which starts again from the window's
own size. Depth and MSAA textures follow. A surface logs a warning when
it becomes fitted.
Why only an Outlook launch hit it (read from the code, not observed):
winit creates the window at CW_USEDEFAULT, so Windows places it, and a
launcher that hands ShellExecute its own window gets new windows on its
monitor. winit then converts the restored logical size at that monitor's
scale: 3440 points became 8600 pixels on a 250% panel. eframe's clamp
compares points against the largest monitor, so it let 3440 through.
Tests: surface_fit with the issue's 8600 x 3423 and edge sizes (zero,
u32::MAX, a zero limit, the longer side exactly on the limit); egui-wgpu
resolves to vendor/egui-wgpu and the fit stays wired into the paths that
size a surface.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* vendor/egui-wgpu: drop the stray .cargo-ok marker
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>