Drop fast-forward from the in-game menu and the second screen

It stays a feature -- the hotkey, the touch button and the OSD indicator are
untouched -- it just no longer occupies a tile in either panel.

Removing it also repairs the Session grid's controller dispatch, which was
broken. EmulationMenuViewModel indexes three things by the same number: the
grid in SessionPane, actionCount(), and activateSelection(). Fast-forward sat
at index 1 in the grid and had no entry in activateSelection, so every index
from 1 up dispatched to its neighbour -- a pad press on Fast Forward restarted
the game, Restart swapped the disc, Swap Disc closed the game, and Close could
not be activated at all. actionCount had been corrected to 5 to let the pad
reach the last tile, which made the misalignment reachable rather than fixing
it. With the tile gone the grid is resume/restart/swap/close, exactly what
activateSelection already dispatched, and actionCount goes to 4.

Touch users are unaffected: the grid is tapped by index through onSelect, so
it was always correct there. This only ever misfired for a controller.
This commit is contained in:
jpolo1224
2026-08-24 13:24:10 -04:00
parent 58ac171af5
commit b9ed11da20
8 changed files with 110 additions and 12 deletions
@@ -47,6 +47,7 @@ struct RPCSXApi {
void (*surfaceSizeChanged)(int width, int height);
void (*setPadSensor)(int port, int x, int y, int z, int g);
int (*getPadRumble)(int port);
void (*setThermals)(float cpu, float gpu, float battery, bool show);
bool (*usbDeviceEvent)(int fd, int vendorId, int productId, int event);
bool (*installFw)(JNIEnv *env, int fd, long progressId);
bool (*isInstallableFile)(jint fd);
@@ -161,6 +162,7 @@ struct RPCSXLibrary : RPCSXApi {
result.surfaceSizeChanged = reinterpret_cast<decltype(surfaceSizeChanged)>(dlsym(handle, "_rpcsx_surfaceSizeChanged"));
result.setPadSensor = reinterpret_cast<decltype(setPadSensor)>(dlsym(handle, "_rpcsx_setPadSensor"));
result.getPadRumble = reinterpret_cast<decltype(getPadRumble)>(dlsym(handle, "_rpcsx_getPadRumble"));
result.setThermals = reinterpret_cast<decltype(setThermals)>(dlsym(handle, "_rpcsx_setThermals"));
result.usbDeviceEvent = reinterpret_cast<decltype(usbDeviceEvent)>(dlsym(handle, "_rpcsx_usbDeviceEvent"));
result.installFw = reinterpret_cast<decltype(installFw)>(dlsym(handle, "_rpcsx_installFw"));
result.isInstallableFile = reinterpret_cast<decltype(isInstallableFile)>(dlsym(handle, "_rpcsx_isInstallableFile"));
@@ -506,6 +508,18 @@ extern "C" JNIEXPORT jint JNICALL Java_net_rpcsx_RPCSX_getPadRumble(
return rpcsxLib.getPadRumble(port);
}
// Device temperatures for the perf overlay. Discovery is the app's job -- Android exposes no
// supported API for SoC temperatures, so it reads the thermal sysfs, whose zone naming and units
// are vendor-specific -- and this only carries the result across.
extern "C" JNIEXPORT void JNICALL Java_net_rpcsx_RPCSX_setThermals(
JNIEnv *, jobject, jfloat cpu, jfloat gpu, jfloat battery, jboolean show) {
if (rpcsxLib.setThermals == nullptr) {
return;
}
rpcsxLib.setThermals(cpu, gpu, battery, show == JNI_TRUE);
}
extern "C" JNIEXPORT void JNICALL Java_net_rpcsx_RPCSX_surfaceSizeChanged(
JNIEnv *, jobject, jint width, jint height) {
if (rpcsxLib.surfaceSizeChanged == nullptr) {
@@ -216,9 +216,6 @@ object SecondScreen {
row1.addView(action(I18n.get("touch.stateAction.load")) {
MainActivityRuntime.instance?.loadState()
}, rowLp())
row1.addView(action(I18n.get("secondScreen.fastForward")) {
MainActivityRuntime.instance?.toggleFastForward()
}, rowLp())
row2.addView(action(I18n.get("secondScreen.pause")) {
// Same toggle the on-screen pause button uses.
if (MainActivityRuntime.eState.value == EmuState.PAUSED) MainActivityRuntime.resume()
@@ -636,12 +636,6 @@ private fun SessionPane(state: EmulationMenuUiState, viewModel: EmulationMenuVie
ActionGrid(
actions = listOf(
MenuAction(str("action.resume"), str("action.play"), "", Success, viewModel::resume),
MenuAction(
str("action.fastForward"),
if (MainActivityRuntime.fastForwardToggleActive) str("action.fastForward.on") else str("action.fastForward.detail"),
"",
if (MainActivityRuntime.fastForwardToggleActive) Success else null,
) { MainActivityRuntime.instance?.toggleFastForward(); viewModel.resume() },
MenuAction(str("memcard.restart"), str("action.reset"), "", null, MainActivityRuntime::restart),
MenuAction(str("action.swapDisc"), str("action.swapDisc.detail"), "", null, MainActivityRuntime::promptSwapDisc),
MenuAction(str("action.close"), MainActivityRuntime.currentGame.value?.title.orEmpty(), "", Danger) {
@@ -381,9 +381,12 @@ class EmulationMenuViewModel(application: Application) : AndroidViewModel(applic
}
private fun actionCount(tab: EmulationMenuTab): Int = when (tab) {
// MUST match SessionPane's action list length. This was 4 against a list of 5, so the pad
// could never reach Close at all.
EmulationMenuTab.Session -> 5
// MUST match SessionPane's action list length, AND activateSelection's Session branch
// below -- all three are indexed by the same number and nothing checks they agree.
// Fast-forward used to sit at index 1 in the grid but had no entry in activateSelection,
// so every index from 1 up dispatched to its neighbour: a pad press on Fast Forward
// restarted the game, and Close could not be activated at all.
EmulationMenuTab.Session -> 4
EmulationMenuTab.Graphics -> 2
EmulationMenuTab.Fixes -> 0
EmulationMenuTab.Performance -> 3
@@ -111,6 +111,13 @@ class RPCSX {
/** What the game is asking the rumble motors to do: (large shl 8) or small, each 0..255. */
external fun getPadRumble(port: Int): Int
/**
* Device temperatures for the perf overlay, in degrees Celsius, or Thermals.NONE for a
* reading that could not be taken. Discovery is the app's job -- Android has no supported
* API for SoC temperatures -- so the core is only ever told the answer.
*/
external fun setThermals(cpu: Float, gpu: Float, battery: Float, show: Boolean)
external fun usbDeviceEvent(fd: Int, vendorId: Int, productId: Int, event: Int): Boolean
external fun processCompilationQueue(): Boolean
external fun startMainThreadProcessor(): Boolean
+10
View File
@@ -19,6 +19,7 @@
#include "Emu/Io/pad_config_types.h"
#include "Emu/RSX/Null/NullGSRender.h"
#include "Emu/RSX/Overlays/overlay_manager.h"
#include "Emu/RSX/Overlays/overlay_perf_metrics.h"
#include "Emu/RSX/Overlays/overlay_save_dialog.h"
#include "Emu/RSX/Overlays/overlay_trophy_notification.h"
#include "Emu/RSX/Overlays/overlay_utils.h"
@@ -4307,6 +4308,15 @@ extern "C" void _rpcsx_setPadSensor(int port, int x, int y, int z, int g) {
// whenever it likes, and there is no notification to hook. The caller reads it on a
// timer and drives the phone's vibrator. Returns 0 when nothing is running, so a
// caller that keeps polling after the game stops simply sees silence.
// Device temperatures, pushed from the app layer -- see the note in overlay_perf_metrics.h for
// why discovery lives there and not here. Values are degrees Celsius, or the 'none' sentinel.
extern "C" void _rpcsx_setThermals(float cpu, float gpu, float battery, bool show) {
rsx::overlays::thermals::g_cpu = cpu;
rsx::overlays::thermals::g_gpu = gpu;
rsx::overlays::thermals::g_battery = battery;
rsx::overlays::thermals::g_show = show;
}
extern "C" int _rpcsx_getPadRumble(int port) {
std::lock_guard lock(g_virtual_pad_mutex);
@@ -640,6 +640,15 @@ namespace rsx
fmt::append(perf_text, "%s%s", perf_text.empty() ? "" : "\n", lsfg);
}
// Device temperatures, for the same reason and in the same place: appended after
// the switch so every detail level gets them without four format strings and
// their positional arguments having to agree. Empty when the option is off or no
// zone was readable, so the overlay is unchanged for anyone it cannot serve.
if (const std::string temps = thermals::status_text(); !temps.empty())
{
fmt::append(perf_text, "%s%s", perf_text.empty() ? "" : "\n", temps);
}
m_body.set_text(perf_text);
if (perf_text.empty())
@@ -940,6 +949,42 @@ namespace rsx
return compiled_resources;
}
namespace thermals
{
atomic_t<f32> g_cpu{none};
atomic_t<f32> g_gpu{none};
atomic_t<f32> g_battery{none};
atomic_t<bool> g_show{false};
std::string status_text()
{
if (!g_show)
{
return {};
}
std::string out;
// Whole degrees. A tenth is noise on a sysfs zone that updates every couple of
// seconds, and this is read at a glance.
const auto add = [&out](const char* label, f32 c)
{
if (c <= none)
{
return;
}
fmt::append(out, "%s%s %d\u00b0C", out.empty() ? "" : " ", label, static_cast<int>(c));
};
add("CPU", g_cpu);
add("GPU", g_gpu);
add("BAT", g_battery);
return out;
}
}
extern void reset_performance_overlay()
{
if (!g_cfg.misc.use_native_interface)
@@ -113,5 +113,33 @@ namespace rsx
};
void reset_performance_overlay();
// Device temperatures, pushed in from the Android app layer.
//
// The core cannot read these and should not learn how: 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, whose zone naming, ordering and
// even units are vendor-specific. That discovery belongs in the app, which already deals
// in Android specifics; this side just holds what it was told and prints it.
//
// Atomic because the writer is a UI-thread poll and the reader is the render thread.
// Relaxed is fine: three independent display values with no ordering relationship to each
// other or to anything else, and the worst a torn read can do is show one stale number
// for one frame.
namespace thermals
{
// Below any real temperature, so one comparison separates "could not read" from
// "cold" without carrying a second flag per value. A device with no readable zone is
// a normal outcome, not an error.
constexpr f32 none = -1000.0f;
extern atomic_t<f32> g_cpu;
extern atomic_t<f32> g_gpu;
extern atomic_t<f32> g_battery;
extern atomic_t<bool> g_show;
// "45°C GPU 43°C BAT 31°C", or empty when off or nothing is readable.
std::string status_text();
}
}
}