01428ced58
apple / swift (push) Failing after 27s
release / apple (push) Failing after 26s
apple / screenshots (push) Has been skipped
windows-host / package (push) Has been cancelled
arch / build-publish (push) Has been cancelled
android / android (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
Add a bounded, trust-agnostic, mDNS-INDEPENDENT QUIC reachability probe and surface it everywhere saved-host presence is shown, so a host reached over a routed network (Tailscale/VPN/multicast-filtering LAN) no longer reads Offline just because it isn't advertising — the display-side companion to the 0.8.4 dial-first connect fix. Core: - punktfunk-core: NativeClient::probe (bounded handshake; a real host answers even on trust mismatch, a wrong/closed/TCP-only port fails) + punktfunk_probe C ABI (ABI_VERSION 3->4, header regenerated). - pf-client-core: trust::probe_reachable_many (parallel per-host sweep). Presence pips now read `advertising OR probed-reachable`, refreshed by a ~10-12s background sweep off the UI thread: - Linux (relm4): ui_hosts probed map + HostsMsg::Probed sweep. - Windows (windows-reactor): pf-probe worker -> HostsProps.probed. - Apple (SwiftUI): HostStore.refreshReachability, driven by HomeView + GamepadHomeView .task. - Android (Compose): nativeProbe JNI seam + periodic LaunchedEffect (LNP-gated), online dot added to the touch HostCard. - Decky already probes via --list-hosts --probe. Decky client: make the flatpak client's known-hosts store the single source of truth via new headless CLI modes (--list-hosts / --add-host / --set-host / --forget-host / --reset / --reachable). The plugin can now add a host by address, edit/forget hosts, reset all state (keeping the client identity), and shows probe-backed online pips — state is shared with the desktop client, not duplicated. Also lands in-progress Android 17 LNP groundwork (targetSdk 37 + ACCESS_LOCAL_NETWORK runtime flow, permission dialogs) that was already present in the working tree. Verified: cargo check + clippy clean (punktfunk-core, pf-client-core, linux, android native); android assembleDebug BUILD SUCCESSFUL; decky typecheck + rollup build clean; probe true/false-positive behaviour exercised against a live host. Windows and Apple were not compiled locally (no MSVC/Xcode on this Linux box). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
4.3 KiB
Rust
94 lines
4.3 KiB
Rust
//! punktfunk Android client — the JNI bridge ("nativecore") over `punktfunk-core`.
|
|
//!
|
|
//! Architecture: the **Rust-heavy** client model (like `punktfunk-client-linux`, *not* the
|
|
//! thin-native-over-C-ABI Apple model). This `cdylib` links `punktfunk-core` directly and drives
|
|
//! the whole `punktfunk/1` protocol through [`punktfunk_core::client::NativeClient`]; Kotlin owns
|
|
//! only the Android-framework surface (Compose UI, `SurfaceView` lifecycle, input capture, the
|
|
//! Wi-Fi `MulticastLock` + permission UX, Keystore). The JNI seam below is the one place the two
|
|
//! languages meet.
|
|
//!
|
|
//! Why Rust-heavy: Kotlin cannot `import` the cbindgen C header the way Swift can, so a native
|
|
//! bridge is unavoidable. Writing it in Rust lets the Android client reuse the Linux client's
|
|
//! orchestration verbatim — audio jitter ring, the VK keymap inverse, latency/skew math, the
|
|
//! input capture state machine, trust/pairing logic, **mDNS discovery** ([`discovery`], the same
|
|
//! `mdns-sd` browse the Linux/Windows clients use) — instead of re-porting it into Kotlin. Kotlin
|
|
//! keeps only the Android-framework surface it must (Compose UI, `SurfaceView`, input capture, the
|
|
//! Wi-Fi `MulticastLock` + permission UX, Keystore identity).
|
|
//!
|
|
//! JNI symbols map to `io.unom.punktfunk.kit.NativeBridge` in the `:kit` Gradle module
|
|
//! (`clients/android`). The surface: the native-link proof (`abiVersion`/`coreVersion`), mDNS host
|
|
//! discovery ([`discovery`]), and the session lifecycle in [`session`] — connect/pair + the trust
|
|
//! surface, the per-plane pumps (video → AMediaCodec, audio ↔ AAudio, mic uplink), input, and
|
|
//! rumble/HID feedback ([`feedback`]). Mode renegotiation is still TODO (see [`session`]).
|
|
|
|
use jni::objects::JObject;
|
|
use jni::sys::jint;
|
|
use jni::JNIEnv;
|
|
|
|
#[cfg(target_os = "android")]
|
|
mod adpf;
|
|
#[cfg(target_os = "android")]
|
|
mod audio;
|
|
#[cfg(target_os = "android")]
|
|
mod decode;
|
|
// Ungated: pure `mdns-sd` + `jni`, so the browse + its JNI seam link into the host workspace build
|
|
// (and its unit test runs there) exactly like `session`/`stats`. Kotlin only ever calls it on device.
|
|
mod discovery;
|
|
mod feedback;
|
|
#[cfg(target_os = "android")]
|
|
mod mic;
|
|
mod session;
|
|
mod stats;
|
|
// Ungated like `discovery`: pure `jni` + `punktfunk_core::wol` (no Android framework), so it links
|
|
// into the host workspace build too. Kotlin only ever calls it on device.
|
|
mod wol;
|
|
// Ungated like `wol`: pure `jni` + `punktfunk_core::client` (the reachability probe). Kotlin calls
|
|
// it off the main thread to light saved-host "online" pips independently of mDNS.
|
|
mod probe;
|
|
|
|
/// Initialize `android_logger` once when the JVM loads the library. Logs land in logcat under the
|
|
/// `punktfunk` tag. Core `tracing` events (transport warnings: socket-buffer clamp, QoS failures)
|
|
/// arrive here too: tracing's "log" feature — declared explicitly in Cargo.toml rather than relied
|
|
/// on via quinn's defaults — forwards them as `log` records since no tracing subscriber is ever
|
|
/// installed. Android-only — there is no JVM (and no logcat) on the host build.
|
|
#[cfg(target_os = "android")]
|
|
#[no_mangle]
|
|
pub extern "system" fn JNI_OnLoad(
|
|
_vm: *mut jni::sys::JavaVM,
|
|
_reserved: *mut std::ffi::c_void,
|
|
) -> jint {
|
|
android_logger::init_once(
|
|
android_logger::Config::default()
|
|
.with_max_level(log::LevelFilter::Info)
|
|
.with_tag("punktfunk"),
|
|
);
|
|
log::info!(
|
|
"punktfunk_android loaded (core ABI v{})",
|
|
punktfunk_core::ABI_VERSION
|
|
);
|
|
jni::sys::JNI_VERSION_1_6
|
|
}
|
|
|
|
/// `NativeBridge.abiVersion(): Int` — the core's C-ABI version. A non-error return is the
|
|
/// scaffold's proof that `System.loadLibrary` found the `.so`, the JNI symbol resolved, and the
|
|
/// linked `punktfunk-core` is the one we expect.
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_abiVersion(
|
|
_env: JNIEnv,
|
|
_this: JObject,
|
|
) -> jint {
|
|
punktfunk_core::ABI_VERSION as jint
|
|
}
|
|
|
|
/// `NativeBridge.coreVersion(): String` — the crate version, proving JNI string marshaling works.
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_coreVersion<'local>(
|
|
env: JNIEnv<'local>,
|
|
_this: JObject<'local>,
|
|
) -> jni::sys::jstring {
|
|
match env.new_string(env!("CARGO_PKG_VERSION")) {
|
|
Ok(s) => s.into_raw(),
|
|
Err(_) => JObject::null().into_raw(),
|
|
}
|
|
}
|