diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/ConnectScreen.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/ConnectScreen.kt index 2225f82..1ac162b 100644 --- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/ConnectScreen.kt +++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/ConnectScreen.kt @@ -212,11 +212,12 @@ fun ConnectScreen( } } - // Wake-aware connect. If the target is a saved host with a learned MAC that ISN'T currently - // advertising (asleep/off), wake it and WAIT for it to reappear on mDNS (WakeController shows the - // "Waking…" overlay) before dialing — discovery stays running meanwhile so we can see it come - // back. A fire-and-forget packet + the connect timeout wasn't enough for a cold boot. Otherwise - // dial straight through. + // Wake-aware connect. If auto-wake is on (Settings.autoWakeEnabled) and the target is a saved + // host with a learned MAC that ISN'T currently advertising (asleep/off, or just missing from + // mDNS), wake it and WAIT for it to reappear on mDNS (WakeController shows the "Waking…" overlay) + // before dialing — discovery stays running meanwhile so we can see it come back. A fire-and-forget + // packet + the connect timeout wasn't enough for a cold boot. Otherwise (auto-wake off, no MAC, or + // already seen live) dial straight through. fun doConnect(targetHost: String, targetPort: Int, name: String, pinHex: String?) { if (identity == null) { status = "Identity not ready yet — try again in a moment" @@ -230,7 +231,7 @@ fun ConnectScreen( fun liveAdvert(): DiscoveredHost? = if (kh != null) discovered.firstOrNull { kh.matches(it) } else discovered.firstOrNull { it.host == targetHost && it.port == targetPort } - if (macs.isNotEmpty() && liveAdvert() == null) { + if (settings.autoWakeEnabled && macs.isNotEmpty() && liveAdvert() == null) { waker.start( hostName = name, connectsAfter = true, diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/MainActivity.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/MainActivity.kt index 4beb5fe..b2764cf 100644 --- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/MainActivity.kt +++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/MainActivity.kt @@ -51,8 +51,12 @@ class MainActivity : ComponentActivity() { * Whether the last console input came from a real gamepad (face buttons / stick) vs. a TV D-pad * remote (which has no A/B/X/Y). The console UI reads this to show glyphs the user recognises — pad * face buttons, or a select glyph + arrows for a remote. Compose observes it (a snapshot state). + * Defaults to the remote glyphs on a TV (its D-pad remote is the typical first input, and often the + * only one) and to gamepad glyphs everywhere else (the console UI on a phone/tablet only activates + * via a real controller, so a TV-remote glyph would be a wrong first impression there) — set from + * [onCreate] once a [Context] is available, then kept live by real input. */ - var lastPadIsGamepad by mutableStateOf(false) + var lastPadIsGamepad by mutableStateOf(true) private set /** The panel's highest-refresh display mode (0 = unknown/unsupported), resolved once at startup. */ @@ -60,6 +64,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + lastPadIsGamepad = !isTvDevice(this) resolveHighRefreshMode() setConsoleHighRefreshRate(true) // the console UI wants max refresh; streaming manages its own // Dark, transparent system bars regardless of the system theme — our UI is always dark, so diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/Settings.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/Settings.kt index 9ddfbdc..8ed1280 100644 --- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/Settings.kt +++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/Settings.kt @@ -62,6 +62,15 @@ data class Settings( * lock and HDMI game-mode signalling stay on regardless — they're harmless. */ val lowLatencyMode: Boolean = true, + /** + * Wake-on-LAN a saved host before connecting when it isn't currently seen on mDNS. On (default): + * a connect to a host with a learned MAC that isn't advertising sends a magic packet and waits + * for it to reappear (see [WakeController]) before dialing. Off: always dial straight through, + * skipping the mDNS-presence check entirely — for a host that's actually up but not visible on + * mDNS (a flaky discovery path, a VLAN/subnet that blocks multicast, etc.), where auto-wake would + * otherwise misfire and wait out its timeout despite the host already being reachable. + */ + val autoWakeEnabled: Boolean = true, ) /** [Settings.touchMode] values; persisted by name. */ @@ -91,6 +100,7 @@ class SettingsStore(context: Context) { gamepadUiEnabled = prefs.getBoolean(K_GAMEPAD_UI, true), libraryEnabled = prefs.getBoolean(K_LIBRARY, true), lowLatencyMode = prefs.getBoolean(K_LOW_LATENCY, true), + autoWakeEnabled = prefs.getBoolean(K_AUTO_WAKE, true), ) fun save(s: Settings) { @@ -110,6 +120,7 @@ class SettingsStore(context: Context) { .putBoolean(K_GAMEPAD_UI, s.gamepadUiEnabled) .putBoolean(K_LIBRARY, s.libraryEnabled) .putBoolean(K_LOW_LATENCY, s.lowLatencyMode) + .putBoolean(K_AUTO_WAKE, s.autoWakeEnabled) .apply() } @@ -129,6 +140,7 @@ class SettingsStore(context: Context) { const val K_GAMEPAD_UI = "gamepad_ui_enabled" const val K_LIBRARY = "library_enabled" const val K_LOW_LATENCY = "low_latency_mode" + const val K_AUTO_WAKE = "auto_wake_enabled" /** Legacy Boolean the enum replaced — read once as the migration default, never written. */ const val K_TRACKPAD = "trackpad_mode" diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/SettingsScreen.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/SettingsScreen.kt index d9f596a..5d5ae1c 100644 --- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/SettingsScreen.kt +++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/SettingsScreen.kt @@ -394,6 +394,14 @@ private fun InterfaceSettings(s: Settings, update: (Settings) -> Unit) { checked = s.libraryEnabled, onCheckedChange = { on -> update(s.copy(libraryEnabled = on)) }, ) + ToggleRow( + title = "Auto-wake on connect", + subtitle = "Send Wake-on-LAN and wait for a saved host to reappear on mDNS before " + + "connecting. Turn off if a host that's already on isn't seen on mDNS, so connects " + + "go straight through instead of waiting out the wake timeout.", + checked = s.autoWakeEnabled, + onCheckedChange = { on -> update(s.copy(autoWakeEnabled = on)) }, + ) ToggleRow( title = "Stats overlay", subtitle = "Show FPS, throughput and latency while streaming (3-finger tap toggles it live)",