Files
7437bde378 M3: draw a GNOME-style title bar on Wayland (#553) (#1530)
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 <11478682+bmh129@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-13 03:04:13 -04:00

9.3 KiB

Developing PdfCraft

Working instructions for agents and contributors are in AGENTS.md and CLAUDE.md. This page collects what the desktop app reads from its environment and where it writes its diagnostics.

Low-disk Cargo profile

When local build storage is constrained, select the opt-in low-disk profile explicitly:

cargo build --profile low-disk -p pdfcraft-cli
cargo test --profile low-disk -p pdfcraft-cos --lib

The profile inherits the normal development settings, including debug assertions and overflow checks. It uses line-table-only debug information for workspace code, which keeps file and line numbers in backtraces but omits local-variable and parameter details, and it disables incremental compilation for this profile only. Ordinary cargo build, cargo test, and release builds keep their existing profiles.

Cargo stores custom-profile artifacts separately under target/low-disk. Switching between the normal and low-disk profiles can therefore leave both sets of artifacts on disk. To remove only the low-disk artifacts, run cargo clean --profile low-disk.

Building PdfCraft requires Rust 1.95 or later: the UI crate and its egui and craft-ui dependencies need it, and the workspace rust-version states the same minimum.

Interface appearance

Display theme saves System, Light or Dark separately from the palette currently drawn. New installations still default to Light. System follows native appearance events on macOS and Windows. The native window keeps its inherited appearance in every mode; both egui style branches use the selected application palette, so desktop changes cannot override a manual choice.

On Linux the native app starts an XDG Settings portal subscription when System is first selected. It reads org.freedesktop.appearance / color-scheme, where numeric 1 requests Dark, 2 requests Light, and 0 means no preference. When that states no preference or is missing, it falls back to org.gnome.desktop.interface / color-scheme (prefer-dark, prefer-light, default), which XFCE's portal answers instead of the freedesktop namespace; KDE answers only the freedesktop one. The subscription is installed before the initial read. Initial results and later SettingChanged signals wake an idle window. There is no periodic desktop polling, and signals received in a manual mode update the cache without requesting frames.

System falls back to Light if no appearance is available, including an absent portal, an unsupported appearance backend, no preference, an unknown value or a failed connection. This does not rewrite the saved System choice. The watcher cancels when its window closes; it does not reconnect after a session-bus disconnection. Headless UI contexts use their supplied egui input and do not contact a desktop session bus. Page and document colours retain their existing behavior.

Logs

The desktop app writes its log records to standard error and to logs/pdfcraft.log in its settings folder, next to app.ron: Linux and FreeBSD ~/.local/share/pdfcraft/logs/ (or $XDG_DATA_HOME/pdfcraft/logs/), macOS ~/Library/Application Support/PdfCraft/logs/, Windows %APPDATA%\PdfCraft\data\logs\. In portable mode (a portable.txt file next to the executable, as the Windows portable zip ships) the settings folder is PdfCraftData\ beside the executable, and crash recovery and new digital IDs go there too. A start from a desktop menu, Finder or the Start menu has no terminal, so this file is what to attach to a bug report: a failed autosave, a page that would not render, a render worker that could not start and the report of an internal error all land there. Each launch moves the previous log to pdfcraft.1.log (and that one to pdfcraft.2.log), so the log of a run that crashed survives the next start. The file stops growing at 16 MiB. --version writes no file.

By default PdfCraft's own crates (pdfcraft*) log at info and everything else at warn. RUST_LOG replaces that with env_logger-style directives, for example RUST_LOG=debug, RUST_LOG=warn,pdfcraft_render=trace or RUST_LOG=info,wgpu_core=warn; a directive ending in * covers every target starting with it (pdfcraft*=debug). The logger is apps/pdfcraft/src/logging.rs. It never records the control-channel token or document passwords.

GPU surface limits

The desktop renderer enables the selected adapter's supported 2D texture extent, while keeping egui-wgpu's other device requirements. Restored windows on mixed-DPI monitors can exceed its default 8192-pixel extent even when the GPU supports a larger surface (#577). The enabled extent never exceeds the adapter's capability. A window larger than even that (restored at another monitor's scale, or stretched across monitors) gets a surface fitted within the limit and is drawn at a correspondingly lower scale instead of panicking in Surface::configure (vendored egui-wgpu, surface_fit; see vendor/README.md). On DX12, PdfCraft's Windows default, the surface is stretched over the window: softer, but complete and lined up with the pointer. wgpu's GL backend copies it unscaled into a corner of the window instead.

For blurry or incorrectly scaled UI reports, attach pdfcraft.log. Look for the successful startup renderer: line, which records the wgpu adapter, device type, backend and enabled texture limit, or the OpenGL renderer. Also look for the drawing it at … and scaling it up warning: it means PdfCraft hit the surface-size safety fallback and rendered below the physical window size, so softness is expected.

Renderer fallback

PdfCraft draws with wgpu, and starts again with eframe's own OpenGL renderer (glow) when wgpu fails before the app is created: no adapter, no device, or a window surface the device can't configure. The last of these was a panic, not an error, because wgpu's default error handler panics and eframe configures the surface before any app code runs; a 2015 Intel GPU whose driver timed out in Surface::configure on wgpu's GL backend closed PdfCraft at launch (#519). Vendored egui-wgpu configures a new window's surface inside winit::catch_errors and returns a validation, out-of-memory or internal error as WgpuError::ConfigureSurface (see vendor/README.md). Still a panic, as upstream: an error in a later reconfigure (resizing, a lost surface); a device lost during that first configure (wgpu reports it only to a callback, so the first frame panics, after the app has started); and a window shown at zero size, which is first configured on resize. If OpenGL can't start either, PdfCraft exits with both errors in the log. The fallback is logged at error, with the wgpu error. Renderer resource creation now uses the same scopes and returns WgpuError::CreateRenderer before the app is created, so a rejected egui_pipeline can also reach this retry instead of the uncaptured-error panic handler. This does not fix the failing driver's shader compilation, and explicit PDFCRAFT_RENDERER=wgpu still disables the retry. PDFCRAFT_RENDERER skips it or chooses OpenGL from the start.

Environment variables

Variable Effect
RUST_LOG Log levels for standard error and the log file (see Logs)
RUST_BACKTRACE 1 adds a backtrace to the report of an internal error
XDG_DATA_HOME Linux/FreeBSD: base of the settings folder (pdfcraft/), the log folder and crash recovery
WGPU_POWER_PREF GPU choice; by default PdfCraft draws on the GPU the Wayland compositor draws with (it says so through linux-dmabuf feedback), else the GPU that drives the primary display (Windows) or the built-in panel (Linux), else the low-power (integrated) GPU. The log says which one was chosen
WGPU_BACKEND Graphics backend; by default Windows uses Direct3D 12, falling back to OpenGL
WGPU_VALIDATION_INDIRECT_CALL 1 turns wgpu's check of indirect draw arguments back on. PdfCraft draws nothing indirectly and turns it off, since the compute shader the check needs fails to compile on some drivers (Metal on an Intel Iris Pro), which lost the device at start-up
PDFCRAFT_RENDERER gl starts with OpenGL (glow) and never loads wgpu; wgpu reports a wgpu failure instead of retrying with OpenGL; unset, wgpu is retried with OpenGL when it can't start (see Renderer fallback)
PDFCRAFT_TITLEBAR Linux: app draws PdfCraft's own title bar (window buttons in the tab strip, in the order of GNOME's button-layout, and resize edges); system keeps the frame winit or the desktop draws; unset, PdfCraft draws its own when the Wayland compositor draws no frames (GNOME), and leaves it to the desktop otherwise (KDE, Sway, Hyprland, X11). Where PdfCraft draws it, Preferences ▸ Use system title bar switches back to winit's frame without a restart; system also hides that option
PDFCRAFT_BUILD_LABEL Build time: a label for a local or patched build (letters, digits, inner dots), shown after the version in --version, About and the log (0.7.1 (titlebar)). packaging/linux/package.sh also puts it in the file names and the .rpm/.deb release (1.titlebar), which upgrades over the release of the same version
CRAFT_FONTS_DIR Build time: a craft-fonts checkout to embed (Japanese fonts)
PDFCRAFT_SYSTEM_FONTS 0 stops the desktop app from using an installed font for characters its embedded fonts lack (cargo xtask screenshots sets it)