feat(android): console UI, wake-on-LAN wait-until-up, host edit + TV/tablet polish
Bring the Android client to parity with Apple's gamepad experience and finish Wake-on-LAN. - Console/gamepad home: host carousel, aurora chrome, mTLS game-library coverflow, and an input-aware legend that switches between gamepad face buttons and a TV-remote select-ring + arrows based on the last-used input. - Wake-on-LAN: the fire-and-forget send is upgraded to wait-until-up (WakeController/WakeOverlay: resend + mDNS poll, 90s timeout, cancel/retry, fingerprint-matched so a host that cold-boots onto a new DHCP IP still connects), plus host edit (touch dialog + console form) with an auto-filled MAC. - Android TV: brand banner (android:banner), density-aware console scaling, D-pad/ remote nav (Up = Settings, Down or the pad Select button = host Options), emergency stream-exit chord, and 120Hz console refresh. - Touch UI: settings split into subpages with a tablet NavigationRail, axis-aware tab animation (horizontal on phones, vertical on the tablet rail), animated settings navigation, and a licenses screen with a back button + the real workspace version (read from Cargo.toml). - Vector Lock/controller icons (no emoji); bundled Geist font. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,11 +12,12 @@ use ndk::media::media_codec::{
|
||||
OutputBuffer,
|
||||
};
|
||||
use ndk::media::media_format::MediaFormat;
|
||||
use ndk::native_window::{FrameRateCompatibility, NativeWindow};
|
||||
use ndk::native_window::NativeWindow;
|
||||
use punktfunk_core::client::NativeClient;
|
||||
use punktfunk_core::error::PunktfunkError;
|
||||
use punktfunk_core::session::Frame;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -113,11 +114,13 @@ pub fn run(
|
||||
mode.height
|
||||
);
|
||||
// Tell the display the stream's refresh so Android can pick a matching display mode and align
|
||||
// vsync (no 60-in-120 judder on high-refresh panels). minSdk 31 ≥ API 30, so the underlying
|
||||
// ANativeWindow_setFrameRate is always present; non-fatal if the platform declines.
|
||||
if let Err(e) = window.set_frame_rate(mode.refresh_hz as f32, FrameRateCompatibility::Default) {
|
||||
log::warn!(
|
||||
"decode: set_frame_rate({} Hz) failed (non-fatal): {e}",
|
||||
// vsync (no 60-in-120 judder on high-refresh panels). `ANativeWindow_setFrameRate` is NDK API 30,
|
||||
// above our API-28 floor, so we resolve it at runtime (see `try_set_frame_rate`) rather than link
|
||||
// it — a hard import would stop `libpunktfunk_android.so` loading at all on API 28/29. Absent
|
||||
// there ⇒ we simply skip the hint (non-fatal; the stream renders fine without it).
|
||||
if mode.refresh_hz > 0 && !try_set_frame_rate(&window, mode.refresh_hz as f32) {
|
||||
log::debug!(
|
||||
"decode: set_frame_rate({} Hz) unavailable/declined (non-fatal)",
|
||||
mode.refresh_hz
|
||||
);
|
||||
}
|
||||
@@ -340,6 +343,32 @@ fn boost_thread_priority() {
|
||||
}
|
||||
}
|
||||
|
||||
/// `ANativeWindow_setFrameRate` (NDK **API 30**) resolved from `libandroid.so` at runtime, so the lib
|
||||
/// still loads on our API-28 floor — a hard import of a >floor symbol makes `dlopen`/`System.load`
|
||||
/// fail on every API-28/29 device, even where this path is never hit. Mirrors the dlsym approach in
|
||||
/// [`crate::adpf`]. Returns `true` when the platform accepted the hint; `false` on API < 30 (symbol
|
||||
/// absent) or when the platform declined. `compatibility` is fixed to the DEFAULT (0) policy.
|
||||
fn try_set_frame_rate(window: &NativeWindow, frame_rate: f32) -> bool {
|
||||
// int32_t ANativeWindow_setFrameRate(ANativeWindow*, float frameRate, int8_t compatibility)
|
||||
type SetFrameRateFn = unsafe extern "C" fn(*mut c_void, f32, i8) -> i32;
|
||||
// SAFETY: `dlopen` of the always-mapped `libandroid.so` (only bumps its refcount; never closed —
|
||||
// process-lifetime handle). `dlsym` returns null when the symbol is absent (device API < 30),
|
||||
// checked before transmuting the non-null pointer to its fn-pointer type. `window.ptr()` is the
|
||||
// live `ANativeWindow` this `NativeWindow` owns for the call's duration.
|
||||
unsafe {
|
||||
let lib = libc::dlopen(c"libandroid.so".as_ptr(), libc::RTLD_NOW);
|
||||
if lib.is_null() {
|
||||
return false;
|
||||
}
|
||||
let sym = libc::dlsym(lib, c"ANativeWindow_setFrameRate".as_ptr());
|
||||
if sym.is_null() {
|
||||
return false; // device API < 30 — no per-surface frame-rate hint
|
||||
}
|
||||
let set_frame_rate = std::mem::transmute::<*mut c_void, SetFrameRateFn>(sym);
|
||||
set_frame_rate(window.ptr().as_ptr().cast(), frame_rate, 0) == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to copy one access unit into a codec input buffer and queue it, without blocking. Returns
|
||||
/// `false` only on `TryAgainLater` (no input buffer free) — the caller keeps the AU pending and
|
||||
/// retries; a hard dequeue/queue error counts as consumed (retrying can't salvage the AU, and
|
||||
|
||||
Reference in New Issue
Block a user