Wires in the option-(a) sidecar approach: the AppImage carries a
bundled AppImageUpdate AppImage and an embedded update-info string
in the .upd_info ELF section. BackendConnection drives both the
check and the apply via QProcess.
BackendConnection:
- Q_INVOKABLE checkForUpdates()
Bundled mode only. Spawns AppImageUpdate.AppImage with
--check-for-update <APPIMAGE>. Exits 0 → noUpdatesAvailable,
1 → updatesAvailable, anything else → updateCheckFailed.
Dev mode: emits updateCheckFailed("…dev-mode only").
- Q_INVOKABLE applyUpdate()
Bundled mode only. Spawns AppImageUpdate.AppImage with
--remove-old <APPIMAGE>. Replaces the running AppImage in
place; user must restart. Emits updateApplied or
updateApplyFailed.
- Sidecar path resolves to applicationDirPath()/AppImageUpdate.AppImage
by default, overridable via BRIDGE_APPIMAGEUPDATE_BIN.
- APPIMAGE env (set by the AppImage runtime) determines the target
file. Outside an AppImage both methods fail loudly.
build-appimage.sh:
- Auto-downloads AppImageUpdate-x86_64.AppImage into the cached
tools dir and copies it into AppDir/usr/bin/AppImageUpdate.AppImage.
- New --update-info flag, forwarded to appimagetool's -u so the
.upd_info ELF section carries an "zsync|<URL>" string the sidecar
will fetch.
examples/todo Makefile forwards APPIMAGE_UPDATE_INFO env to the
script as --update-info.
release.yml:
- Builds the AppImage with APPIMAGE_UPDATE_INFO set to the canonical
Gitea Releases asset URL for this tag.
- Installs zsync, runs zsyncmake to generate Todo-x86_64.AppImage.zsync.
- Generates a JSON appcast (latest.json) with version / url / sha256 /
size / zsync URL / released_at — useful as an HTTP-fetchable
fallback for clients that prefer a structured manifest.
- SHA256SUMS now covers AppImage + zsync + latest.json.
- Uploads all four assets to the Gitea Release.
AppImage size grows from ~104 MB to ~152 MB with the sidecar bundled.
Embedding verified: objdump shows .upd_info populated with the
expected zsync URL after a local build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
php-qml
A framework for building native desktop applications with a Symfony / FrankenPHP backend and a Qt/QML frontend, packaged as a single distributable per OS.
Status
Planning stage. The architectural design lives in PLAN.md. No implementation exists yet — first code lands in Phase 0 (a throwaway transport spike). See the roadmap.
What it is
php-qml lets a PHP developer write a desktop application using ordinary Symfony on the backend and ordinary QML on the frontend. The two halves run as a process pair inside one bundled binary:
- A Qt/QML host process owns the window, input, and rendering.
- A bundled FrankenPHP child runs a Symfony application in worker mode.
- They communicate over a local socket — HTTP for commands and queries, Mercure SSE for state push.
The framework provides the lifecycle, transport, reactive models, and scaffolding so application code stays idiomatic on both sides.
What it is not
Not a PHP↔Qt language binding. It does not embed PHP into a Qt event loop and it does not generate Qt classes from PHP. The two languages run in separate processes; the bridge is a wire protocol, not an FFI layer.
If you've watched php-gtk and php-qt go quiet, that is the failure mode this project deliberately avoids — the framework owns the boring parts (lifecycle, transport, conventions) so it doesn't depend on a single maintainer keeping a language binding alive.
Tech stack
- Backend: PHP 8.x, Symfony, Doctrine ORM, FrankenPHP (worker mode), Mercure
- Frontend: Qt 6 LTS, QML, C++ plugin where required
- Build: CMake, Composer
- CI: Gitea Actions, Gitea Releases
- Targets: Linux (AppImage), macOS (
.app+.dmg), Windows (NSIS / MSIX)
Getting started
Nothing to run yet. Once Phase 0 lands:
- Clone the repository and check out the
devbranch. - Install Qt 6 and PHP 8.x; the FrankenPHP runtime is fetched at build time.
task dev— starts FrankenPHP in watch mode and launches the Qt host pointed at it.
Detailed setup will be documented alongside the framework skeleton in Phase 1.
Project structure
See PLAN.md §9 for the intended layout. The repo currently contains only PLAN.md and this README.
Roadmap
Six phases, each ending with something runnable. Detail in PLAN.md §13.
- Phase 0 — throwaway spike, prove transport on Linux.
- Phase 1 — framework skeleton, dev mode, single-instance lock, CI quality gate.
- Phase 2 — reactive models, update semantics, headline maker (
make:bridge:resource). - Phase 3 — POC todo application generated via the makers; testing infrastructure.
- Phase 4 — bundled mode, per-OS packaging, release CI, auto-update.
- Phase 5 — DX polish.
Contributing
Active development happens on the dev branch; main only carries release commits. Pull requests target dev.
A CONTRIBUTING.md will be added with the framework skeleton in Phase 1.
Versioning
Semantic Versioning — MAJOR.MINOR.BUGFIX. MAJOR for breaking changes, MINOR for backwards-compatible features, BUGFIX for backwards-compatible fixes.
License
To be decided before the first release. The framework's own code will be permissively licensed; note that Qt is shipped under LGPL and that carries obligations for distributors — see PLAN.md §12 (Qt LGPL relinkability).