On Linux the save dialog is the XDG desktop portal's, shown through
rfd. rfd sends no `current_filter` and reads only the file name from
the portal's answer, and the GTK file chooser keeps the typed name
when another file type is picked. With "PDF-compatible .ai" picked,
`poster` typed for a new document was saved as a native
`poster.vectorcraft`, and so was the suggested
`Untitled-1.vectorcraft` left as it was.
When a save offers two or more file types (Save As, Save a Copy, Save
as Template and the first Save of a new document), the app now calls
the portal's SaveFile itself over D-Bus. The dialog starts on the
suggested file's type, and its answer names the type picked. A name
typed without an extension, or the suggested name left as it was,
gets that type's extension. A name typed with another save format's
extension, other than the suggested file's (for Save as Template, a
template format's), is saved in that format, and any other extension
gets the picked type's added. The dialog keeps the window as its
parent on X11 and Wayland, and rfd shows the dialog when the portal
can't. The same code runs on the BSDs; Windows, macOS and the web
build keep their dialogs.
Docs: docs/target-app-parity.md.
On a Wayland session a drawing tablet's pen could not draw or click:
winit 0.30 does not bind the Wayland tablet protocol, and neither KWin
(since Plasma 6.3) nor GNOME's Mutter turns tablet input into pointer
input. Linux Display Server's X11 (XWayland) choice let the pen work as
a mouse, but it opened the window through XWayland on every start, also
without a tablet.
Linux Display Server has a third choice, X11 (XWayland) When a Pen Is
Attached (x11WithPen). It opens the window through XWayland on a
Wayland session only when /proc/bus/input/devices lists a pen as the
app starts: a device with the BTN_TOOL_PEN key and the ABS_X and ABS_Y
axes, the check PhotoCraft's tablet.rs makes. Automatic stays the
default. On Linux the Performance page shows a note for tablet pens
that don't draw.
README.md named KDE alone; the KWin commit's message says Mutter does
not emulate tablet events either, so README.md and docs/development.md
now name GNOME. The Pen pressure row of docs/hardware-parity.md said
Linux has pressure through XWayland; winit 0.30 reports none on X11
either.
Docs: README.md, docs/development.md, docs/hardware-parity.md,
docs/target-app-parity.md.
On a machine busy with other builds, five tests on main failed now and
then and passed when run alone. The canvas test for dragging an object
with raster effects gave the render worker 10 s for its frame, which
took 0.10 s alone and 5.8 to 9.4 s with four ui-egui test runs at
once. The three tests_sysclip_probe tests gave the clipboard probe's
thread 2 or 3 s. The control server's persistent-requests test sent
each request with writeln!, which sends the JSON to the socket in many
small writes, and a pause of more than the test's 120 ms line deadline
between two of them closed the connection.
The canvas test now waits up to 60 s for the worker, as another test in
canvas.rs does. The probe tests wait up to 60 s too. The control server
test sends each request line in one write and keeps its 120 ms line
deadline. Only test code changes.
Each swatch or graphic style library now opens in a panel of its own,
`library:<kind>:<library id>`, in the shared docking system: several can be open
at once, and they float, group as tabs, dock and close like any panel.
Opening a library that is open brings its panel forward; Previous / Next
Library show the neighbouring library in the same panel.
The library panel menu gains Persistent. The panels' view, Show Find Field
and Persistent are kept in the UI preferences (`library_panels`); at startup
the persistent panels reopen where the saved layout had them, a library
loaded from a file read again from its path (a missing file is skipped with
a status-bar note). `ui.libraryPanel {panel, library?, view?, showFind?,
persistent?}` reaches all of it; docs/mcp.md documents the panels.
Install Plug-in (plugin.install) now keeps the plug-in's module in a
Plug-ins folder next to the preferences, as Illustrator keeps plug-ins in
its Plug-ins folder, and the desktop app and vectorcraft-cli install the
ones there at startup (a module that doesn't load is skipped with a log
record, at most 512 MB are read). Remove Plug-in deletes it from the
folder. The Additional Plug-ins Folder preference is still loaded from
where it is. Confined MCP servers and VECTORCRAFT_NO_PREFS keep nothing,
as with the PDF preset and colour profile stores.
Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md.
When pressing Ctrl+P to open the Print dialog and clicking Setup (or Settings), print.printerSetup previously discarded the selected printer name and unconditionally launched explorer ms-settings:printers. On Windows 10, this opened the Windows Settings Printers and scanners page, displaying the list of installed printers instead of the printer driver printing preferences.
Launch Windows native Printing Preferences dialog for the target printer using rundll32.exe printui.dll,PrintUIEntry /e /n printer, falling back to the system default printer or Settings if no printer is available or detected. Add unit tests covering command construction and fallback behavior.
Fixes#2569.
After the desktop save panel closes, the app adds the file type's
extension to a name typed without it (or with one the save does not
write). The panel asked only about the name as typed, so on Linux
typing `card` with `card.vectorcraft` in the folder wrote over
`card.vectorcraft` without a question. On Linux the save panel is the
XDG desktop portal, and its GTK and GNOME file choosers return the
name as typed. Windows and macOS add the extension themselves only
when the panel lists file types, and the panels of Document Info's
Save, the presets dialogs' Export…, the CSS Properties export, the
swatch and graphic style library saves and the Libraries panel's
export list none.
When the added extension names a file that exists, Save, Save As, Save
a Copy, Save as Template, the exports and the other writes through a
save panel now ask "“card.vectorcraft” already exists. Replace it?"
first (dialog `replaceFile`). OK runs the write again with the same
answer and replaces the file. Cancel writes nothing and shows the
dialog it was asked over again. The host reports whether a file exists
through a new `Services::exists`.
Docs: docs/control-protocol.md, docs/target-app-parity.md.
The EPS reader read PostScript by any name, but the Open and Place
dialogs hid .ps and .epsf files, and a .ps or .epsf file whose header
is only %! could not be opened or placed ("not a format VectorCraft
reads"). The reference app lists .eps, .epsf and .ps under Open and
Place.
- A read-only PostScript format (ps) joins the format table. A .ps
file opens and places through the EPS reader and reports the
ps format. document.export does not write .ps; file.print does.
- .epsf is an EPS extension.
- OPEN_EXTS and PLACE_EXTS list epsf and ps, so the dialogs, the CLI
help and the MCP open_file description list them.
UNASSOCIATED_EXTS lists them too: the macOS bundle declares neither.
- A .ps or .epsf file whose header is only %! now pastes as EPS when
copied in a file manager, as one with a %!PS header already did, and
clipboard.importEps reads PostScript whose header is only %!, which
it rejected before.
Docs: docs/mcp.md, docs/file-format-parity.md (.ps row),
docs/target-app-parity.md.
PDF presets are kept one file each in a PDF Presets folder, ICC profiles as copies in a Color Profiles folder, next to the preferences; the desktop app and vectorcraft-cli use them. Adds color.removeProfile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS, the save sheets of Save As, Save a Copy and the first Save of
a new document have a Format menu again, and the Save as Template sheet
now has one too. The panel is an NSSavePanel built in
apps/vectorcraft/src/mac_save_format.rs, and the menu is its accessory
view, set before beginSheetModalForWindow:completionHandler: shows the
sheet. The menu from #1235 was added after rfd had begun the sheet, and
recent macOS versions dismissed the sheet at once (#1800, #1831,
#1518); #1855 removed it.
Choosing a format changes the extension in the name field and puts
that format's extension first in the allowed file types, which AppKit
adds to a name typed without one. A name typed with another listed
extension is saved in that format. Save panels with one file type and
the open and folder panels are still shown by rfd.
Docs: docs/target-app-parity.md, docs/ui-parity.md.
A folder or app bundle, a link to another kind of file, or a file with
any other extension is refused with a message instead of being handed to
the system's default app. On Windows, a path containing a quotation mark
is refused before it reaches Explorer.
The install folder is where the user sees the app: next to VectorCraft.app on
macOS (nothing is written inside the signed bundle) and next to the .AppImage
file on Linux ($APPIMAGE; the mounted executable's folder is read-only).
The first time VectorCraftData is created, the settings in the user's profile
are copied into it, so a portable zip that gains portable.txt keeps its users'
settings; the originals stay, a second start copies nothing, a copy that fails
halfway is logged and left as it is.
VECTORCRAFT_NO_PREFS never looks for the marker or creates the folder; a
portable copy skips the one-time read of DrawCraft's old settings; the
unwritable-folder warning goes to the log only (eprintln! panics on a closed
standard error).
When a graphics driver crashed the process while the window started,
as Intel's Direct3D 12 driver (igd12umd64.dll) did on an Intel UHD
Graphics 605 (#2243), the next start picked the same adapter again. The
restart without a failing adapter and the first-frame watch run inside
the process, so the crash ended them along with it.
Each start now writes graphics-start.json in the log folder before the
graphics start, adds the adapter once one is picked, and clears both
when the first frame reaches the screen or the app ends without
crashing. A start that finds an earlier start still recorded there
leaves that adapter out, as a restart does, logs it and shows a note in
the status bar. When the crash came while the adapters were being
listed (wgpu's Direct3D 12 backend creates a device on each GPU to list
it), the next starts list OpenGL's adapters alone, except on macOS. The
adapters stay left out until the app version or the Graphics Processor
preference changes, a start shows its first frame on one, or no adapter
is left. WGPU_BACKEND and WGPU_ADAPTER_NAME choose regardless of the
record.
Docs: docs/development.md, docs/target-app-parity.md.
--help and -h used to be taken for a file to open: from a terminal, SSH or an
agent's shell the window opened, or the start hung or failed without a display.
They now print the desktop app's options and point to vectorcraft-cli for
headless work, exiting before logging, preferences or the window, as
--version does.
The Color Picker gets an eyedropper button beside the New and Original
chips, and `ui.sampleScreenColor {stroke?, picker?}` shows the system's
color sampler. The command answers at once. When the person clicks a
color anywhere on the screen, the color becomes the fill or the stroke
through paint.setFill / paint.setStroke as one undo step, or with
`picker` it becomes the open Color Picker's color, which previews on the
selection as any pick does. Escape cancels the sampler. The sampled sRGB
color goes through Cms::srgb_to_rgb, so in a Display P3 working space a
color inside sRGB shows as it did on the screen. `ui.inspect` reports
`screenSampler` while the sampler is open.
- macOS uses NSColorSampler (apps/vectorcraft/src/mac_screen_color.rs).
- The web app uses the browser's EyeDropper API where the browser has
one.
- Without a sampler (Windows, Linux, other browsers) the button is gray
and the command returns an error. Keyboard Shortcuts lists the
command, so it can be given a key.
Docs: docs/control-protocol.md, docs/target-app-parity.md.
The worker answered "timeout" after 60 s and left the request on the queue, so
the app ran it whenever it next drained the queue: an edit nobody was waiting
for, and a second one if the caller had retried. A request now leaves the
queue once: taken by the app, or given up by its caller, whichever comes
first. The timeout reply says which ("will not run" / "still running").
The preferences file was written only when the app quit normally, so settings
confirmed with OK were lost when the session ended any other way (the system
shutting down, the process being ended, a crash). OK now asks the host to
write the file at once.
The Format menu added to the save sheet after rfd began it (#866) made recent
macOS versions (26.6.2 and 27.0.1 reported) dismiss the sheet at once: Save As,
Save a Copy and Save of an untitled document flashed and answered as cancelled,
while panels without it (Open, Export As) worked. The accessory is withdrawn
until it can be set before the sheet is presented; the format still follows
the extension typed in the name field, as before #866.
Fix#1726 with idle, absolute line and reply-write deadlines and connection-slot guards. Add real-loopback regressions and document reconnect behavior.
Validated with cargo xtask ci: all seven gates passed; 6156 tests passed, zero failed, seven existing ignored.
winit 0.30 gives an undecorated window WS_CAPTION | WS_BORDER; on some Windows
systems the compositor still reserves that frame, so the content is drawn
offset while the pointer isn't, and the cursor no longer points at what is under
its tip. Strip the two styles once and again each frame (winit restores them on
maximize/restore), as PhotoCraft does (storytold/photocraft#2401).
The control server wrote each reply with writeln!(out, "{r}") on the
bare TcpStream. serde_json formats a Value one piece at a time, and
each piece (a brace, a quotation mark, a key, a colon, a number) was
sent as a write of its own: the 17 MB document.inspect reply of a
document with 50,000 objects on one layer was sent in 7,857,200 writes.
On Windows 11, sending that reply to a client over loopback took 34 s,
and sending the 28 MB document.json reply took 57 s.
vectorcraft-cli mcp reads every reply from a running app over this
connection.
write_line now writes each reply, each refusal and the "too many
control connections" line through a 64 KiB BufWriter, then flushes
it. The 17 MB reply takes 262 writes. Sending it now takes 0.09 s,
and sending the 28 MB reply takes 0.2 s. The bytes are the same.
Docs: docs/target-app-parity.md.
Paste read PDF only from the Windows clipboard and EPS nowhere, so on macOS a copied
equation or drawing pasted nothing new (the last thing copied in the app came back).
- macOS: the pasteboard's PDF, EPS and SVG are read, ahead of text and bitmaps.
- Windows: EPS is read beside PDF.
- Everywhere: an .eps file copied in a file manager pastes.
- clipboard.importEps loads an EPS into the clipboard as document.open reads one.
--control took the next argument as its port and dropped it when it wasn't a number, so the app
started without a control server and `--control --automation-read-root DIR` opened DIR as a
document. automation_args::ControlArg now reads --control <port> or --control=<port> once and
requires a port from 1 to 65535; VECTORCRAFT_CONTROL_PORT must be one too, checked only when
--control isn't given so --control and --version win over a bad value. Errors go through the
same exit as the root flags (#1157).
Tests: unit tests in automation_args.rs; tests/automation_args.rs runs the binary for each bad
form and checks the exact message and a prompt failing exit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Save serializes JSON straight into the compressor and writes images from their Arc into the
file; open reads each image into its own buffer, and NodeKind reads its tag first instead of
buffering every node. Already-compressed images are stored rather than deflated again in the
same gzip stream. Limits apply per section, so any file VectorCraft writes opens again. Data
Recovery writes each image once, content-addressed, instead of every interval. Native files open
on a background thread. Files stay readable by main both ways; JSON-only saves are identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
Localize native/web file filters, diagnostic labels, numbered presets
and contributor tooltips with English fallback and Czech plural forms.
Fix gradient adjective agreement and disambiguate Japanese printer marks
in PDF/Print choices and summaries. Keep file extensions and names intact.
Add Czech catalog rows and regression coverage; update localization evidence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 display rule put it first,
and the window stayed empty: the compositor took the frames and showed
nothing, with no error, so neither the restart on a failed adapter (#502)
nor the first-frame watch (#964) noticed.
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 under Automatic adapter_order ranks
that adapter ahead of the display GPUs. No answer (X11, an older compositor,
a GPU no adapter matches) leaves the existing rules in charge; a user's own
choice, WGPU_ADAPTER_NAME and the restart skip list still come first.
wayland-client and wayland-protocols are already in the tree through winit;
they become direct Linux-only dependencies.
wgpu's default DX12 shader compiler loads whichever dxcompiler.dll the
DLL search path finds first; VectorCraft ships none, so it was another
program's, and one without its dxil.dll warns that the DXIL is unsigned.
wgpu takes the warning for a compile error, the device is lost before
the first frame, and the app falls back to OpenGL. FXC (d3dcompiler_47,
part of Windows) compiles everything the window needs, as LightCraft
does since its #484; WGPU_DX12_COMPILER still picks another.
winit 0.30 delivers no file drops on native Wayland. The new Linux Display
Server preference (Automatic / X11 (XWayland), shown on Linux only, read at
startup) has winit open the window on X11 when the session is Wayland,
DISPLAY is set and the X11 libraries winit loads are installed (ldconfig
check ported from PhotoCraft); otherwise the window opens on Wayland and
the log says why. README, development notes, parity docs, ROADMAP and the
complete catalogs updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The crash: rfd's blocking dialog ran -[NSSavePanel runModal] inside
winit's event handler; when a resize of the panel ends, AppKit runs the
closures winit queued meanwhile, and winit's re-entrancy panic fires
inside an Objective-C block, where it can't unwind, so the app aborts.
As PhotoCraft (#673) and PdfCraft (#145) ship it:
- macOS: picks::ask shows an rfd::AsyncFileDialog as a sheet on the
window (beginSheetModalForWindow returns at once); a waiter thread
sends the paths and wakes the window. A pick asked outside an entry is
refused and logged instead of running a modal. Linux keeps its thread
(#592) and now also wakes the window; Windows is unchanged.
- Nine paths that asked outside an entry now run as entries: Load
Profile, Swatches and Graphic Styles Other Library, the shortcut
editor's Import and Export, and the presets dialogs' Export.
- While a dialog is open: the macOS menu bar is inert except Hide, Hide
Others and Minimize; Quit, Close, Close All and the close button are
refused; Finder/Dock opens and the IME discard wait; another dialog
is refused with a status message. The answer re-activates the
document that asked (by uid) and Save Changes finds its document by
uid; a frame guard clears a stale picks entry after a recovered panic.
- ui.inspect reports fileDialog.
Squashed onto current main by the maintainer (with main's automation
sandbox wrappers and font-name menus kept).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>