Show device temperatures on the performance overlay

Ported from ARMSX2. Android has no supported API for SoC temperatures --
HardwarePropertiesManager is gated behind the signature-level DEVICE_POWER --
so the only route is the thermal sysfs, which is readable without permission
on essentially every device but is not a contract: zone count, order, naming
and even the unit are all vendor-specific. So the app discovers zones once by
name, tolerates every failure by having no reading, and never displays a value
it could not actually read. A device that exposes nothing shows nothing, which
is a normal outcome rather than an error.

The core cannot read these and should not learn how, so the app pushes them in
through _rpcsx_setThermals into atomics beside the overlay. The overlay line is
appended after the detail-level switch, the same way the frame generation line
is, so all four levels get it without their format strings and positional
arguments having to agree.

The sentinel is -1000.0f on both sides. ARMSX2 spells the Kotlin half
Float.MIN_VALUE, which in Kotlin is the smallest POSITIVE float (1.4e-45) and
so arrives as a real temperature of 0 degrees rather than as absent; worth
correcting there too.

Default on, poll interval configurable 1-5s (default 2s) since sensor overhead
was the concern raised when this was asked for. No realtime option: a
temperature that moves slower than a second is not worth the syscalls.
This commit is contained in:
jpolo1224
2026-08-24 13:27:11 -04:00
parent b9ed11da20
commit 6ab1a4b456
3 changed files with 32 additions and 0 deletions
@@ -905,6 +905,10 @@ val EN: Map<String, String> = mapOf(
"pad.rightStickFeel.title" to "Right Stick Feel",
"pad.players.help" to "The PS3 has seven controller ports and no multitap, so up to seven pads work with no setup. Connect them before launching — the order they first press a button in is the order they are assigned.",
"pad.rumble.description" to "Master switch for controller rumble and the device's built-in vibration. Turn off to silence all haptics.",
"overlay.toggle.temps" to "Device temperatures",
"overlay.toggle.temps.description" to
"Show CPU, GPU and battery temperature on the performance overlay. Not every device exposes these \u2014 one that doesn't simply shows nothing.",
"overlay.tempInterval.label" to "Temperature poll interval",
"pad.rumble.label" to "Rumble / Vibration",
"pad.rumblePhone.label" to "Vibrate the phone",
"pad.rumblePhone.description" to
@@ -2158,6 +2158,9 @@ open class MainActivityRuntime : ComponentActivity() {
// Restore the saved rumble master toggle into the native gate (NativeApp.onPadRumble).
NativeApp.sRumbleEnabled = ControllerMappings.rumbleEnabled()
NativeApp.sPhoneRumbleEnabled = ControllerMappings.phoneRumbleEnabled()
// Starts the temperature poll if the overlay wants it. Costs one file read every couple
// of seconds and stops entirely when the option is off.
runCatching { com.armsx2.Thermals.load(this) }
// Push the saved haptic strength + achievement-sound volume into their native gates before
// any rumble or unlock sound can fire (both default to 1.0 = as authored until set here).
ControllerMappings.syncHapticIntensity()
@@ -244,6 +244,31 @@ fun OverlayTab(state: MutableState<Settings>) {
ffToasts.value = it
com.armsx2.runtime.MainActivityRuntime.prefs.edit { putBoolean("ui.hotkeyToasts", it) }
}
SettingsDivider()
// Device temperatures on the perf overlay. Android exposes no supported API for SoC
// temperatures, so these come from the thermal sysfs, whose zone naming and units are
// vendor-specific -- a device with no readable zone simply shows nothing here.
val ctx = androidx.compose.ui.platform.LocalContext.current
ToggleRow(
str("overlay.toggle.temps"),
com.armsx2.Thermals.osdEnabled.value,
description = str("overlay.toggle.temps.description"),
) {
com.armsx2.Thermals.setOsdEnabled(ctx, it)
}
// Poll interval. Asked for explicitly as the mitigation for sensor overhead. No
// "realtime" option: a temperature that moves slower than a second is not worth the
// syscalls.
if (com.armsx2.Thermals.osdEnabled.value) {
IntSliderRow(
label = str("overlay.tempInterval.label"),
value = com.armsx2.Thermals.intervalSec.value,
min = 1,
max = 5,
valueFormatter = { "${it}s" },
onChange = { com.armsx2.Thermals.setIntervalSec(it) },
)
}
}
}