Before cancelling a conversation notification in response to an inline reply, post one final update that appends the outgoing text to the MessagingStyle history, attributed to the local user. This gives assistants such as Android Auto a tick to observe the sent message in the notification's message history and surface a 'reply sent' style confirmation before markConversationRead cancels the notification. Extract the 'me' Person construction into buildMePerson() and share it between showGroupSummary and createConversationNotification. The conversation builder now optionally takes an extraOutgoingMessage which is appended to the MessagingStyle (actions and when-timestamp continue to be anchored on the last incoming message). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|---|---|---|
| .. | ||
| src | ||
| .gitignore | ||
| build.gradle.kts | ||
| entitlements.plist | ||
| proguard-rules.pro | ||
| README.md | ||
:desktop — Meshtastic Desktop
A Compose Desktop application target — the first full non-Android target for the shared KMP module graph. This module serves as:
- First multi-target milestone — Proves the KMP architecture supports real application targets beyond Android.
- Build smoke-test — Validates that all
core:*KMP modules compile and link on a JVM Desktop target. - Shared navigation proof — Uses the same Navigation 3 routes from
core:navigationand the sameNavDisplay+entryProviderpattern as the Android app, proving the shared backstack architecture works cross-target. - Desktop app scaffold — A working Compose Desktop application with a
NavigationRailfor top-level destinations and placeholder screens for each feature.
Quick Start
# Run the desktop app
./gradlew :desktop:run
# Run tests
./gradlew :desktop:test
# Package native distribution (DMG/MSI/DEB) — debug (no ProGuard)
./gradlew :desktop:packageDistributionForCurrentOS
# Package native distribution (DMG/MSI/DEB) — release (ProGuard minified)
./gradlew :desktop:packageReleaseDistributionForCurrentOS
ProGuard / Minification
Release builds use ProGuard for tree-shaking (unused code removal), significantly reducing distribution size. Obfuscation is disabled since the project is open-source. Rules are aligned with the Android R8 rules in app/proguard-rules.pro — both targets share the same anti-class-merging philosophy.
Configuration:
build.gradle.kts—buildTypes.release.proguardblock enables ProGuard withoptimize.set(true)andobfuscate.set(false).proguard-rules.pro— Keep-rules for reflection/JNI-sensitive dependencies (Koin, kotlinx-serialization, Wire protobuf, Room KMPandroidx.room3, Ktor, Kable BLE, Coil, SQLite JNI, Compose Multiplatform resources) and an anti-merge rule for Compose animation classes.
Key rules:
- Compose animation anti-merge (
-keep class androidx.compose.animation.** { *; }) — Prevents ProGuard's optimizer from incorrectly tree-shaking or merging animation class hierarchies (e.g.EnterTransition/ExitTransitioninto*Impl), which causes animations to silently snap. Same rule as Android. - Room KMP — Uses
androidx.room3package path (Room KMP 3.x).
Troubleshooting ProGuard issues:
- If the release build crashes at runtime with
ClassNotFoundExceptionorNoSuchMethodError, a library is loading classes via reflection that ProGuard stripped. Add a-keeprule inproguard-rules.proand the corresponding rule inapp/proguard-rules.proto keep both targets aligned. - To debug which classes ProGuard removes, temporarily add
-printusage proguard-usage.txtto the rules file and inspect the output indesktop/proguard-usage.txt. - To see the full mapping of optimizations applied, add
-printseeds proguard-seeds.txt. - Run
./gradlew :desktop:runReleasefor a quick smoke-test of the minified app before packaging.
Architecture
The module depends on the JVM variants of KMP modules:
core:common,core:model,core:di,core:navigation,core:repositorycore:domain,core:data,core:database,core:datastore,core:prefscore:network,core:resources,core:ui
Navigation: Uses JetBrains multiplatform forks of Navigation 3 (org.jetbrains.androidx.navigation3:navigation3-ui) and Lifecycle (org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose, lifecycle-runtime-compose). A unified SavedStateConfiguration with polymorphic SerializersModule is provided centrally by core:navigation for non-Android NavKey serialization. Desktop utilizes the exact same navigation graph wiring (settingsGraph, nodesGraph, contactsGraph, connectionsGraph) directly from the commonMain of their respective feature modules, maintaining full UI parity.
Coroutines: Requires kotlinx-coroutines-swing for Dispatchers.Main on JVM/Desktop. Without it, any code using lifecycle.coroutineScope or Dispatchers.Main (e.g., NodeRepositoryImpl, RadioConfigRepositoryImpl) will crash at runtime.
DI: A Koin DI graph is bootstrapped in Main.kt with platform-specific implementations injected.
UI: JetBrains Compose for Desktop with Material 3 theming. Desktop acts as a thin host shell, delegating almost entirely to fully shared KMP UI modules. Includes native macOS notification support (via TrayState and bundleID identification) and a monochrome SVG tray icon for a native look and feel.
Notifications: Implements the common NotificationManager interface via DesktopNotificationManager. Repository-level notifications (messages, node events, alerts) are collected in Main.kt and forwarded to the system tray. macOS requires a consistent bundleID (configured in build.gradle.kts) and the NSUserNotificationAlertStyle key in Info.plist for notifications to appear correctly in the distributable.
Localization: Desktop exposes a language picker, persisting the selected BCP-47 tag in UiPreferencesDataSource.locale. Main.kt applies the override to the JVM default Locale and uses a staticCompositionLocalOf-backed recomposition trigger so Compose Multiplatform stringResource() calls update immediately without recreating the Navigation 3 backstack.
Key Files
| File | Purpose |
|---|---|
Main.kt |
App entry point — Koin bootstrap, Compose Desktop window, theme + locale application |
DemoScenario.kt |
Offline demo data for testing without a connected device |
ui/DesktopMainScreen.kt |
Navigation 3 shell — NavigationRail + NavDisplay |
navigation/DesktopNavigation.kt |
Nav graph entry registrations for all top-level destinations (delegates to shared feature graphs) |
radio/DesktopRadioTransportFactory.kt |
Provides TCP, Serial/USB, and BLE transports |
notification/DesktopMeshServiceNotifications.kt |
Real implementation of notification triggers for Desktop |
DesktopNotificationManager.kt |
Bridge between repository notifications and Compose TrayState |
radio/DesktopMeshServiceController.kt |
Mesh service lifecycle — orchestrates want_config handshake chain |
radio/DesktopMessageQueue.kt |
Message queue for outbound mesh packets |
di/DesktopKoinModule.kt |
Koin module with stub implementations |
di/DesktopPlatformModule.kt |
Platform-specific Koin bindings |
stub/NoopStubs.kt |
No-op implementations for all repository interfaces |
What This Validates
| Module | What's Tested |
|---|---|
core:common |
Base64Factory, NumberFormatter, UrlUtils, DateFormatter, CommonUri |
core:model |
DeviceVersion, Capabilities, SfppHasher, platformRandomBytes, getShortDateTime, Channel.getRandomKey |
core:ui |
Shared Compose components compile and render on Desktop |
| Build graph | All core modules compile and link without Android SDK |
Roadmap
- Implement real navigation with shared
core:navigationroutes (Navigation 3 shell) - Adopt JetBrains multiplatform forks for lifecycle and navigation3
- Implement native macOS/Desktop notification support with
TrayStateand system tray - Wire
feature:settingscomposables into the nav graph (first real feature — ~30 screens) - Wire
feature:nodecomposables into the nav graph (node list with shared ViewModel + NodeItem) - Wire
feature:messagingcomposables into the nav graph (contacts list with shared ViewModel) - Add JetBrains Material 3 Adaptive
ListDetailPaneScaffoldto node and messaging screens - Implement TCP transport (
DesktopRadioTransportFactory) with auto-reconnect and backoff retry - Implement mesh service controller (
DesktopMeshServiceController) with fullwant_confighandshake - Create connections screen using shared
feature:connectionswith dynamic transport detection - Replace 5 placeholder config screens with real desktop implementations (Device, Position, Network, Security, ExtNotification)
- Add desktop language picker backed by shared
UiPreferencesDataSource.localewith live translation updates - Wire remaining
feature:*composables (map) into the nav graph - Move remaining node detail and message composables from
androidMaintocommonMain - Add serial/USB transport for direct radio connection on Desktop
- Add BLE transport (via Kable) for direct radio connection on Desktop
- Add MQTT transport for cloud-connected operation
- Package as native distributions (DMG, MSI, DEB) via CI release pipeline