- C 69%
- Dart 29.7%
- Kotlin 0.6%
- C++ 0.3%
- CMake 0.3%
#79 asked whether desktopIsLocked is a redundant round trip. It is not - a subscribe never answers with the current state, so a poll is the only way to learn it - but checking that turned up that the call does not work. IsLockedRequest answers with an empty frame and puts the state in command_status: OK for locked, ERROR for unlocked. So hasDesktopStatus() never matched and the loop under the call was dead, leaving _lockStatusKnown without a baseline; and on an unlocked device the generic non-OK handling rejected the call into _start's catch, tripping the disconnected flag until the next frame cleared it. unlock() had its own copy, its throw swallowed by a bare catch. desktopIsLockedNow reads the status back out, as an extension on FlipperClient in the library's own idiom beside FlipperGpsApi and FlipperNetworkApi rather than a private method on the one page that needed it: the trap is as wide as the API, so the next caller reaching for client.desktop... meets the cooked call instead of rediscovering the defect. It belongs upstream on FlipperDesktopApi and says so. Only the bare ERROR means unlocked. Matching on the status rather than the exception type is deliberate, since FlipperRpcGeneralException is also the bucket every unmapped status falls into - so firmware without the command throws instead of reporting a device as unlocked. The polled answer seeds the baseline without flashing. It is a snapshot taken before the subscribe landed, so it can be older than a push already applied, and after a pause it is the first thing seen since - an unlock the user did on the device by hand was announced as though it had just happened in the app. unlock() still flashes, which is the point there. A status rejection no longer flags the whole session disconnected either: the poll runs last, so the link has answered twice by then, and treating a command-level rejection as a dropped connection is the same mistake one status further along. unlock() logs now, since a refused unlock had looked exactly like a successful one. Four mutations caught, including the one that showed the first version of this fix was untested: inverting ERROR to mean locked passed every test it had. The fake built one exception type for every status and could not return frames, so it goes through exceptionFromResponse now. Closes #79, closes #94 |
||
|---|---|---|
| .github | ||
| .vscode | ||
| android | ||
| assets | ||
| fastlane/metadata/android | ||
| ios | ||
| lib | ||
| linux | ||
| macos | ||
| screenshots | ||
| test | ||
| translations | ||
| web | ||
| windows | ||
| .gitattributes | ||
| .gitignore | ||
| .gitmodules | ||
| .metadata | ||
| analysis_options.yaml | ||
| CHANGELOG.md | ||
| crowdin.yml | ||
| firebase.json | ||
| l10n.yaml | ||
| LICENSE | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
A Flutter companion app for Flipper Zero, built for Unleashed and other firmware.
BLE/USB connection, firmware updates and DFU repair, remote control, archive,
file manager and a toolbox — one codebase, five platforms.
qUnleashed is a Flutter companion app for Flipper Zero, written from scratch as an alternative to Flipper Android App and tailored for custom firmware, first of all Unleashed firmware by DarkFlippers. The app is optimized around Unleashed-specific workflows: tracking firmware releases, reading changelogs, downloading updates and installing them directly from the app, while also reworking and extending parts of the original Flipper app experience.
Features
qUnleashed keeps the familiar companion-app workflows from the original Flipper Android App and extends them for custom firmware: USB support on mobile and desktop, firmware repair from DFU mode, phone GPS/internet sharing, a stronger local archive and a bigger toolbox.
Connection
- BLE and USB on every platform that allows it. qUnleashed connects to Flipper Zero over BLE on all supported platforms, and over a USB serial cable on Android, Windows, macOS and Linux (iOS is BLE-only — Apple does not expose USB serial to apps). Both transports drive the same app surface: device info, archive sync, file manager, apps, remote control and CLI.
- Multiple devices. The app remembers known Flippers, keeps warm sessions for them, switches between devices instantly and reconnects automatically (a plugged-in USB device takes priority over the last BLE one).
- Reliable background link. On Android a foreground service keeps the BLE connection alive while the app is in the background.
Firmware
- Updater. Firmware-centric for Unleashed: release/dev channel tracking with changelogs, downloads and in-app install, including Unleashed variants (base, extra apps, compact). Official firmware channels are supported as well.
- Repair from DFU mode. If the firmware is broken, qUnleashed can fully re-flash the device over USB in DFU/recovery mode — core firmware, radio stack and FUS handling — mirroring qFlipper's Full Repair flow.
- Release notifications. Optional push notifications for new Unleashed and official firmware releases, with a separate opt-in for dev-channel builds.
Device
- Device info. Connected Flipper status expanded with firmware version and build date, battery details, internal/external storage state and a raw device-details sheet.
- GPS and internet sharing. On firmware builds with the RPC GPS/network extension, the app feeds the phone's location to Flipper apps and proxies network connections from the Flipper through the phone's internet, with live TX/RX traffic stats on the device card.
- Remote desktop. Live screen streaming and button control, physical-keyboard input on desktop, screenshot export and GIF recording of the Flipper screen.
- Command line. A full CLI terminal session on the device (USB; not available on iOS).
Archive
- Local-first per-device archive with synced, remote-only, local-only and deleted states, favorites, restore actions and local/remote deletion.
- Categories. Sub-GHz, Wardriving (including autosaves), RFID 125, NFC, Infrared, iButton, Bad USB and JavaScript files.
- Launch from the app. Saved keys can be emulated/launched on the connected Flipper straight from the archive — NFC, RFID and iButton via RPC emulation, Sub-GHz, Infrared, Bad USB and JavaScript via the matching Flipper app.
- Editing and sharing. Built-in text editor with Flipper file-format syntax highlighting, plus sharing of remote files.
- File Manager. Browse both
/extand/int, hidden-file visibility, folder creation, rename, delete, download, upload and direct text editing.
Apps
- Apps catalog. Search, categories, sorting, app details and screenshots on top of the official catalog, with install, update and delete actions and detection of already-installed and preinstalled apps.
Tools
- Pixel Draw. A pixel-art editor for the Flipper screen: project manager, multi-frame animations, dolphin animation import and live preview directly on the device display.
- Extract MIFARE Keys. MIFARE Classic Mfkey32 key recovery from device nonces, computed natively on the phone.
- Remotes Library. Official infrared remote source plus the public
Lucaslhm/Flipper-IRDBrepository: browse by brand/category, search the whole library, preview and edit.irfiles, save remotes into the local archive or send them straight to/ext/infrared. - Pulse Plotter. Visualize raw Sub-GHz/Infrared/RFID pulse captures with zooming, histograms and slicing helpers — open captures right from the archive.
- Saved Locations. Parses synced archive files for latitude/longitude metadata, shows them as pins on an interactive map with the current phone position, distance, bearing and walking time, and links each pin back to the saved Flipper file.
Translations
The app is translated on Crowdin. Do not edit translations/app_*.arb by hand — English is the only exception, because it is the source every other language is translated from.
Crowdin exports each language file whole, so an edit made here is not merged with the next sync, it is reverted by it. That has already cost one manual repair of the Russian translation.
- To correct or add a translation, work in Crowdin. It reaches this repository automatically, in a pull request opened by the sync.
- To add or change an English string, edit
translations/app_en.arb. It is uploaded to Crowdin when the change lands onmain, and translators see it from there. - To have a new language enabled, open an issue asking for it. Enabling a language is done in Crowdin; once it has translations, the sync adds
translations/app_<code>.arbon its own and the app offers the locale. The only thing the repository needs is the language's own name in_localeNames(lib/services/localization/controller.dart) — without it the picker lists the language by its code rather than asDeutsch.
The Google Play listing works the same way: short_description.txt, full_description.txt and the changelogs are translated on Crowdin, so edit only the en-US copies. Screenshots, the icon, title.txt and video.txt are not on Crowdin and can be changed in any locale.
A CI check enforces all of this, so a pull request that edits a translated file fails rather than being merged and quietly undone.
Writing strings
- Keep product names untranslated: qUnleashed, Flipper, Flipper Zero, and protocol names such as Sub-GHz, NFC, RFID, iButton.
- Give every new key an
@keydescription saying where it appears and, for a bare word, whether it is a verb or a noun. Translators see only the string and that description. - Name placeholders for what they hold (
{fileName}, not{arg0}); the name is shown to translators.
Support ApertureFox projects
GRAM (TON): UQAntlM9gsL92ODiNxNbH4SPpfL5OpYm2gbbuNYZuE9vDEYKScreenshots