Skip to content

v1.2.0 — WinImage .imz, CD-R capacity meter, every flavor of Linux, real crash fix

Choose a tag to compare

@pacnpal pacnpal released this 10 May 20:12
· 41 commits to main since this release

FloppyBootCD v1.2.0 — WinImage .imz, CD-R capacity meter, every flavor of Linux, real crash fix

A genuine feature release. v1.2.0 lands two long-asked-for capabilities — direct WinImage .imz support and a live CD-R capacity meter — plus six new Linux artifact formats, on top of finally fixing the GUI crash that survived two earlier patch attempts.

✨ New features

WinImage .imz compressed floppy images

WinImage's .imz format (ZIP-wrapped raw floppy image) is the most common shipping format for vintage floppy archives — every "boot disk image" pack on archive.org and bbsarchives uses it. Drop them directly into a project; the inner image is extracted to the build's staging tree, so MEMDISK still sees a normal raw floppy. No manual unzipping, no separate tool, no temp file shepherding.

  • File dialog and drag-drop now accept .imz alongside .img / .ima / .vfd / .flp
  • Project validation catches every .imz failure mode up-front (non-ZIP container, no floppy member inside, encrypted, unsupported compression method, 0-byte inner, corrupt CRC) — no more mid-build stack traces
  • Image labels show the uncompressed inner size, not the archive size, because that's what actually lands on the burned disc
  • Project files store only the original .imz path; extraction happens entirely in the throwaway staging dir and never touches user paths
  • Extension allowlist is now centralized in floppybootcd.core.image_prep (FLOPPY_EXTS / COMPRESSED_EXTS / ALL_ACCEPTED_EXTS) — single source of truth for the file dialog, drag-drop, and inner-member detection

CD-R capacity meter

A new permanent status-bar label, Disc usage: X.X MiB / 700 MiB, refreshed on every project change. Turns amber near the limit and red when over capacity, so you find out the project doesn't fit before you click Build instead of mid-xorriso.

  • validate_project blocks builds whose payload exceeds 700 MiB usable capacity with an actionable message ("remove images or burn to DVD media") so xorriso doesn't fail mysteriously on a write that wouldn't have fit
  • Capacity math uses the uncompressed inner-image size for .imz containers and the VESA background bitmap when in vesa-menu mode (since that gets staged onto the disc), giving an accurate pre-build budget
  • All capacity reporting uses MiB (binary 700 × 1024 × 1024) to match the on-disc math — described as "700 MiB CD-R" since binary 700 MiB (734 MB decimal) fits comfortably in the 80-min CD-R's real 737 MB capacity
  • Inner-size lookups are LRU-cached by (path, mtime, size), so a 50-image project doesn't re-read every ZIP central directory on every keystroke

Linux: AppImage, .deb, .rpm — and tarball

Linux now ships in four formats per arch (six new artifacts total), so picking an install matches how you install software on your machine — not "extract this tarball and figure out the rest."

Format Best for Install
AppImage Any distro, "double-click to run" chmod +x *.AppImage && ./*.AppImage
.deb Debian, Ubuntu, Raspberry Pi OS, Pop!_OS, Mint sudo apt install ./*.deb
.rpm Fedora, RHEL, Rocky, Alma, openSUSE sudo dnf install ./*.rpm
.tar.gz Read-only filesystems, locked-down servers, advanced users tar xzf *.tar.gz && cd floppybootcd && ./floppybootcd

Each format ships for both x86_64 and arm64 (Raspberry Pi 3/4/5/Zero 2 W on a 64-bit OS included).

The .deb and .rpm packages declare their Qt runtime dependencies (libxkbcommon, libxcb-*, libegl, fontconfig, libdbus, …) so apt/dnf surface a clear error on a stripped-down install — instead of the silent Qt "could not find platform plugin xcb" failure earlier versions could hit.

How it's built: PyInstaller bundle staged under /usr/lib/floppybootcd/ with a /usr/bin wrapper, an XDG .desktop entry, and the floppy-disk PNG icon. fpm wraps that into .deb and .rpm with per-distro dependency name lists (apt's libxcb-icccm4 vs. dnf's xcb-util-wm). appimagetool wraps an AppDir variant whose AppRun resolves the binary via $APPDIR so the image is relocatable. CI runs appimagetool with --appimage-extract-and-run so the GitHub runner doesn't need FUSE2.

🐛 Fixes

GUI crash on build/burn (real fix, supersedes v1.1.2 and v1.1.3)

Clicking Build ISO or Burn crashed the app within seconds — EXC_BAD_ACCESS on macOS, silent disappearance on Linux/Windows. v1.1.2 was a partial fix that just moved the crash from "first progress update" to "first dialog after finish." v1.2.0 is the real fix.

Root cause: the build/burn worker emits Qt signals (progress, log, finished) from its QThread to update GUI widgets on the main thread. In Qt/C++, the auto-selected connection type would queue these across threads. In PySide6, plain Python methods used as slots are wrapped in a "proxy" QObject whose thread affinity ends up wrong, so the queued events were being delivered to the worker thread's event loop — and QProgressBar.setValue() called repaint() off the GUI thread → "Recursive repaint detected" → segfault. v1.1.2 tried Qt.QueuedConnection explicitly, but the proxy was still wrong-threaded; the events queued onto the worker.

Real fix: decorate every cross-thread slot with @Slot(...). That bypasses the proxy entirely and makes the method a native Qt slot bound to its self QObject's thread. Reference: Martin Fitzpatrick's PySide6 @Slot explainer — "the only situation where you need @Slot decorators is when working with threads." The QThread::wait: Thread tried to wait on itself warning in earlier crashes was the smoking gun that the slot ran on the worker thread (where self._build_thread.wait() is the thread waiting on itself).

.imz polish (during stabilization)

  • Reject empty-inner (0-byte) .imz files at validation time with a clear message instead of letting them fail mid-build
  • Strip duplicated filename prefixes from .imz validation error messages (was "Image 1 (foo.imz): foo.imz: …")
  • Catch mid-extraction BadZipFile/OSError/RuntimeError/NotImplementedError and re-raise as the same user-facing ValueError, so no .imz variant can produce a mid-build stack trace
  • The capacity label no longer recomputes on every keystroke; refreshes only when inputs that actually affect it change (image add/remove, menu-style switch, project load)
  • A previously-missing file edited to a valid path no longer stays styled red until the project is reloaded
  • Missing files render as "missing" in the image label; only files that exist but fail to probe show "? (invalid .imz)"

Pages workflow reliability

The site auto-update pipeline that publishes https://pacnpal.github.io/floppybootcd/ had three compounding bugs all surfaced by recent releases:

  1. Unicode escape blowup. Release names with em-dashes encoded as — by json.dumps, then re.sub parsed them as regex template escapes → re.error: bad escape \u. Fixed with ensure_ascii=False and lambda-replacement (which bypasses template parsing).
  2. Race with binary upload. Pages workflow fired immediately on tag push, but release.yml was still building binaries — so it injected the just-created release with assets: [], and the client renderer flashed through "(not in this release)" rows before flipping to "Couldn't fetch release info." Fixed by chaining: Pages now runs on workflow_run: ["Release Binaries", types: completed] and the build job walks the releases list to pick the most recent one with assets | length > 0.
  3. Renderer overwrote good content with errors. The client render() would generate placeholder rows and then call renderError() when nothing matched, even if a preloaded snapshot or earlier fetch had already populated a valid table. Rewritten to return success/failure and to only show errors when no data source produced a useful render.

Tests

  • New coverage: .imz with empty inner image, capacity label states (empty / raw / .imz uncompressed accounting / over-capacity styling / within-capacity), VESA background counted vs. text-menu background ignored, drop-event Windows path normalization, encrypted .imz hex-patching walks every ZIP signature instead of stopping at the first match

📦 Downloads

Platform File
macOS (Apple Silicon) floppybootcd-1.2.0-macos-arm64.zip
macOS (Intel) floppybootcd-1.2.0-macos-x86_64.zip
Windows (x86_64) floppybootcd-1.2.0-windows-x86_64.zip
Windows (ARM64) floppybootcd-1.2.0-windows-arm64.zip
Linux AppImage (x86_64) floppybootcd-1.2.0-linux-x86_64.AppImage
Linux AppImage (ARM64) floppybootcd-1.2.0-linux-arm64.AppImage
Debian / Ubuntu (x86_64) floppybootcd-1.2.0-linux-x86_64.deb
Debian / Ubuntu (ARM64) floppybootcd-1.2.0-linux-arm64.deb
Fedora / RHEL (x86_64) floppybootcd-1.2.0-linux-x86_64.rpm
Fedora / RHEL (ARM64) floppybootcd-1.2.0-linux-arm64.rpm
Linux tarball (x86_64) floppybootcd-1.2.0-linux-x86_64.tar.gz
Linux tarball (ARM64 / Raspberry Pi 3-5) floppybootcd-1.2.0-linux-arm64.tar.gz

Or upgrade in place: uv tool upgrade floppybootcd.

🙏 If you tried v1.1.0 – v1.1.4 and gave up

The crash was real, the first attempt to fix it was wrong, and the recovery shipped one bug at a time before this consolidation. v1.2.0 has been traced against the actual PySide6 thread-affinity rules with citations and end-to-end verification — and the WinImage support people had been asking for is in.

What's Changed

  • Add WinImage .imz (compressed floppy image) support by @pacnpal in #4

Full Changelog: v1.1.4...v1.2.0