* macOS: build the DMG with hdiutil create so the app passes codesign --strict
hdiutil makehybrid -hfs put a com.apple.FinderInfo on every file in the
image, so the signed app inside the DMG (and any ditto copy of it) failed
codesign --verify --deep --strict. That is why verify.sh had dropped
--strict. Build the image with hdiutil create -srcfolder (retried, since
create can hit "Resource busy" on CI), clear build-time xattrs before
signing, and make verify.sh check the app in the finished DMG and a ditto
copy of it with --strict again.
Ported from storytold/pdfcraft#823 and storytold/lightcraft#959.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS DMG: branded window background and icon layout
The launcher's DMG opened as a plain Finder window, unlike every other
Craft app. Add packaging/macos/dmg/ the same way WordCraft and PhotoCraft
did: a background (the ArtCraft mark whole on its blue, title, Ink and
Paper), a .DS_Store with the window size and icon positions, and the
generate.py that writes both reproducibly. The volume is now named
"ArtCraft Launcher" without the version, which the background alias needs;
the DMG file name keeps the version.
Fixes#20
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
wgpu's default DX12 compiler (Auto) loads whichever dxcompiler.dll the DLL
search path finds. The launcher ships none, and without a matching dxil.dll
the unsigned-DXIL warning fails wgpu's indirect-validation pipeline, the
device is lost and the launcher exits before the first frame. Pin the
compiler to FXC, which is part of Windows; WGPU_DX12_COMPILER still
overrides it. GL stays in the Windows backends: it is the deliberate
fallback for machines whose D3D12 adapters can't present to the window.
Fixes#126
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
A launcher started from the Dock, the Start menu or at login has no
terminal, and the Windows build has no console, so its log records and
panic reports were lost.
env_logger now writes each record to standard error and to
logs/artcraft-launcher.log in the launcher's data folder (portable mode
included). Each start keeps the previous two runs' logs; the file is
capped at 16 MiB. The file is opened once this is the only launcher
running, so a second start doesn't rotate the first one's log away.
Panics are reported through the logger. The default filter is now info
for the launcher's crates and warn for the rest; RUST_LOG still wins.
Ported from storytold/pdfcraft#239.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
tray-icon's default id is "<pid>-<n>" and the title is empty, so
StatusNotifierItem hosts showed numbers like "61965-1". On Linux and the
BSDs, give the icon the id "artcraft-launcher" and the title
"ArtCraft Launcher". Windows and macOS are unchanged.
Fixes#136
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
The footer laid out the check status and the rate-limit notice first at
their full width, then put the links, social icons and platform label in a
right-to-left layout in whatever space was left. When the left side was
wider than that space (720 and 960 px, and even 1180 px while rate limited)
the two drew on top of each other.
The right side now comes first and keeps its size; the status and notice
share what is left. The notice gets priority: it switches to a short form
("GitHub limit hit; back in N min", full text on hover) when the full one
won't fit, and the check status is cut short with "…" (new
widgets::hud_elided). Below about 300 px of room for the status the
platform label is dropped.
A UI test renders the rate-limited footer at 720, 960 and 1180 px and checks
the notice and status don't intersect any link, icon or the platform label.
Fixes#16
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Issue #29's window wasn't 0.1.2: its tagline and header are 0.1.0's, and
it offered Download where a portable 0.1.2 offers Update and restart. A
0.1.0 launcher sat in the tray; starting 0.1.2 asked it to show its
window and exited, and that window still offered 0.1.1, found while 0.1.1
was the latest release.
A second start now says hello with its version and the running launcher
answers with its own. A newer start makes the running one quit (unless
it's downloading or installing; then it shows its window) and takes over
the port. The same version or older shows the running window as before.
Launchers from before the handshake hang up without an answer and can't
be asked to quit, so the new start runs anyway. A bare "show" from an
old launcher is still understood.
Tests: the handshake over loopback, including a running launcher that
predates it.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Fix Show in Explorer opening Documents for paths with spaces
reveal() built `/select,<path>` as one argument and passed it with
Command::arg, which quotes any argument containing a space. The Craft
apps' MSIs install under C:\Program Files, so Explorer received
"/select,C:\Program Files\...\app.exe", didn't recognise the switch and
opened its default folder, Documents.
The argument is now `/select,"<path>"`, with only the path quoted and
forward slashes turned into backslashes, passed with raw_arg so Rust
leaves it alone. The argument building is a plain function with unit
tests that run on every platform.
Fixes#39
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Refuse a path with a quote in it; give the Explorer tests their own module
A " would end the quoted /select path early and pass the rest to Explorer as
switches. The module name clashed with the pgrep tests that landed in #81.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Find AppImages outside ~/Applications; add a menu entry for installed ones
Detection only looked in ~/Applications and only for the exact release
file name, so an AppImage the user kept in Gear Lever's ~/AppImages or in
~/.local/bin, or one renamed to photocraft.appimage (Gear Lever's rename,
lower-case extension), showed as not installed. Detection now also looks
in ~/AppImages and ~/.local/bin, accepts either case of the extension, and
matches only the release name or the bare app name (version unknown for
the latter). Updates still install into ~/Applications and only remove
old copies there; AppImages the user keeps elsewhere are never deleted.
Installing an AppImage now writes ai.storyteller.<app>.desktop under
$XDG_DATA_HOME/applications (the host's ~/.local/share when the launcher
runs as a Flatpak) and copies the app's bundled 128 px icon into
icons/hicolor/128x128/apps. Exec is quoted per the Desktop Entry spec;
Categories and StartupWMClass match each app's own .desktop file. The
entry carries X-ArtCraft-Launcher=true and the record lists the files
written, so the launcher only replaces or removes what it wrote (plus an
entry the app wrote for itself that runs the copy being replaced or
removed). The .desktop text is a pure function with unit tests.
Fixes#14Fixes#37
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Remove only AppImages the launcher installed; rebase onto main
AppImages detected in ~/AppImages or ~/.local/bin belong to the user (or Gear
Lever): Remove now deletes only those in ~/Applications or recorded by the
launcher, and explains otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
The launcher offered a .deb whenever apt-get and dpkg were on PATH, and
an .rpm only when dpkg was absent. Fedora can have apt and dpkg
installed (mmdebstrap and friends pull them in), so there it downloaded
the .deb and ran apt-get on it, with .rpm ruled out entirely (#21).
The distro family now comes from ID and ID_LIKE in /etc/os-release:
Debian and Ubuntu get .deb, Fedora, RHEL, CentOS, SUSE and openSUSE get
.rpm, each only with pkexec and the matching tools. Arch and unknown
distros get no system package. After the distro's package, AppImage now
ranks ahead of Flatpak, whose bundles need a Flathub runtime. Installing
a .deb without apt-get fails with a clear message, and removing one falls
back to dpkg -r.
The parsing and the choice are pure functions in core's new distro
module, tested for Fedora, Rocky, openSUSE, Ubuntu, Mint, Arch and an
unknown distro.
Fixes#21
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
ureq only reads HTTPS_PROXY, ALL_PROXY and friends, so a proxy set in
the OS was ignored and GitHub stayed out of reach where it needs one
(#30). Http::new now picks the proxy once and gives it to every agent:
the environment variables first, as before, else the OS setting.
On Windows that is the manual proxy in HKCU Internet Settings, in
either the single host:port form or the per-protocol
http=...;https=... form (the https entry wins), with ProxyOverride as
the bypass list. On macOS it is the HTTPS (else HTTP) proxy from
scutil --proxy with its exceptions list. Linux and the BSDs keep
using the environment. Loopback always goes direct. The chosen proxy
is logged at info level without credentials.
ureq's own win-system-proxy feature treats the whole ProxyServer
value as one address, which breaks the per-protocol form, so the
parsing lives here in pure, tested functions. PAC scripts and WPAD
are not supported.
Fixes#30
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Issue #44: the footer kept saying "GitHub's hourly limit was reached"
when it hadn't been, or long after it stopped mattering.
A 403 or 429 from the API counted as rate limited until
x-ratelimit-reset. GitHub sends that header with every answer, so a
secondary limit (quota left, Retry-After: 60) became up to an hour of
"hourly limit reached". The reset now only counts when
x-ratelimit-remaining is 0; otherwise Retry-After decides, or a minute
on a bare 429, as GitHub documents.
The notice also stayed up after the ArtCraft feed had answered every
app. A check now reports the limit only when some app had to come from
the release pages or the saved answers.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>