From efc335673d87fdbda02f362b2968011a1cccbba3 Mon Sep 17 00:00:00 2001 From: XxJSONDeruloxX Date: Sun, 25 Oct 2026 11:15:46 -0400 Subject: [PATCH] fix: persist SDL3 controller settings --- .../com/example/dsedge/EmbeddedBridgeTest.kt | 31 +++++++++++++++++++ .../0003-persist-controller-settings.patch | 23 ++++++++++++++ third_party/sdlbridge/upstream.json | 3 +- tools/prepare_controller_sources.py | 1 + tools/tests/test_controller_sources.py | 10 ++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 third_party/sdlbridge/patches/0003-persist-controller-settings.patch diff --git a/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt b/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt index 9a1e73958..c79a2db17 100644 --- a/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt +++ b/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt @@ -4,10 +4,15 @@ import android.app.Application import android.content.ComponentName import android.content.ContextWrapper import android.content.pm.PackageManager +import android.view.KeyEvent +import android.widget.LinearLayout +import android.widget.SeekBar +import java.lang.reflect.Proxy import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.Before import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -17,6 +22,32 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [28], application = Application::class) class EmbeddedBridgeTest { + @Before fun resetSettings() { + Settings::class.java.getDeclaredField("prefs").apply { isAccessible = true }.set(null, null) + } + + @Test + fun keyboardSliderChangesAreSavedWithoutATouchDrag() { + val app = RuntimeEnvironment.getApplication() + Settings.init(app) + val activity = Robolectric.buildActivity(BridgeActivity::class.java).get() + val root = LinearLayout(activity) + val sinkType = BridgeActivity::class.java.declaredClasses.single { it.simpleName == "IntSink" } + val sink = Proxy.newProxyInstance(sinkType.classLoader, arrayOf(sinkType)) { _, method, args -> + if (method.name == "set") Settings.gyroGain[1] = args[0] as Int + null + } + Settings.gyroGain[1] = 100 + BridgeActivity::class.java.declaredMethods.single { it.name == "addSlider" } + .apply { isAccessible = true }.invoke(activity, root, "Gyro speed", 800, 100, sink) + val slider = root.getChildAt(1) as SeekBar + assertTrue(slider.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT))) + assertTrue(slider.progress > 100) + assertEquals(slider.progress, app.getSharedPreferences(Host.prefs(app, "bridge"), 0) + .getInt("gyroGain_p1", -1)) + } + @Test fun calibrationUsesOnlyTheSelectedPlayer() { Settings.init(RuntimeEnvironment.getApplication()) diff --git a/third_party/sdlbridge/patches/0003-persist-controller-settings.patch b/third_party/sdlbridge/patches/0003-persist-controller-settings.patch new file mode 100644 index 000000000..e4467b7f5 --- /dev/null +++ b/third_party/sdlbridge/patches/0003-persist-controller-settings.patch @@ -0,0 +1,23 @@ +--- a/java/com/example/dsedge/BridgeActivity.java ++++ b/java/com/example/dsedge/BridgeActivity.java +@@ -454,12 +454,16 @@ + sb.setProgressTintList(ColorStateList.valueOf(ACCENT)); + sb.setThumbTintList(ColorStateList.valueOf(ACCENT)); + sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { ++ private boolean tracking; + @Override public void onProgressChanged(SeekBar s, int v, boolean fromUser) { + t.setText(label + ": " + v); +- if (fromUser) sink.set(v); +- } +- @Override public void onStartTrackingTouch(SeekBar s) { } +- @Override public void onStopTrackingTouch(SeekBar s) { Settings.save(); } ++ if (fromUser) { ++ sink.set(v); ++ if (!tracking) Settings.save(); ++ } ++ } ++ @Override public void onStartTrackingTouch(SeekBar s) { tracking = true; } ++ @Override public void onStopTrackingTouch(SeekBar s) { tracking = false; Settings.save(); } + }); + root.addView(t); + root.addView(sb); diff --git a/third_party/sdlbridge/upstream.json b/third_party/sdlbridge/upstream.json index 8aa840297..0a1520880 100644 --- a/third_party/sdlbridge/upstream.json +++ b/third_party/sdlbridge/upstream.json @@ -70,6 +70,7 @@ }, "patches": [ "patches/0001-droiddeck-integration.patch", - "patches/0002-controller-state-fixes.patch" + "patches/0002-controller-state-fixes.patch", + "patches/0003-persist-controller-settings.patch" ] } diff --git a/tools/prepare_controller_sources.py b/tools/prepare_controller_sources.py index 6a6bff7f5..f6efbb44e 100644 --- a/tools/prepare_controller_sources.py +++ b/tools/prepare_controller_sources.py @@ -23,6 +23,7 @@ def prepare(root=ROOT, output=None): output = output or build / 'controller-sources' if output.is_symlink() or output.resolve() == build.resolve() or not output.resolve().is_relative_to(build.resolve()): raise ValueError('Controller sources must be generated below app/build') + output = output.resolve() output.parent.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory(prefix='controller-sources-', dir=output.parent) as temporary: stage = Path(temporary) diff --git a/tools/tests/test_controller_sources.py b/tools/tests/test_controller_sources.py index c3b53a97e..1876e10af 100644 --- a/tools/tests/test_controller_sources.py +++ b/tools/tests/test_controller_sources.py @@ -1,6 +1,7 @@ import difflib import hashlib import json +import os from pathlib import Path import tempfile import unittest @@ -42,6 +43,15 @@ class ControllerSourcesTest(unittest.TestCase): prepare(self.root) self.assertEqual('keep', (output / 'previous').read_text()) + def test_accepts_a_relative_output_path(self): + previous = Path.cwd() + try: + os.chdir(self.root) + prepare(self.root, Path('app/build/relative')) + finally: + os.chdir(previous) + self.assertEqual(b'embedded\n', (self.root / 'app/build/relative/sdlbridge/java/Input.java').read_bytes()) + def test_fails_when_an_upstream_update_breaks_a_patch(self): vendor = self.root / 'third_party/sdlbridge' (vendor / 'upstream/java/Input.java').write_bytes(b'new upstream\n')