From a080e4a4b4291f9e7c8ba81d5538eedbffdbbe55 Mon Sep 17 00:00:00 2001 From: XxJSONDeruloxX Date: Sun, 25 Oct 2026 10:15:23 -0400 Subject: [PATCH] fix: preserve Android controller fallback --- app/src/main/cpp/sdl3bridge/src/main.c | 3 +-- .../com/droiddeck/launcher/ui/SdlBridgeSettings.kt | 4 +++- .../java/com/example/dsedge/BridgeActivity.java | 2 +- .../main/java/com/example/dsedge/BridgeService.java | 1 + app/src/main/java/com/example/dsedge/Host.java | 5 +++++ .../main/java/com/example/dsedge/NativeBridge.java | 4 ++-- .../java/com/example/dsedge/EmbeddedBridgeTest.kt | 13 +++++++++++++ docs/development/sdl3-controllers.md | 4 +++- 8 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/main/cpp/sdl3bridge/src/main.c b/app/src/main/cpp/sdl3bridge/src/main.c index 773cdbd1f..85790bd30 100644 --- a/app/src/main/cpp/sdl3bridge/src/main.c +++ b/app/src/main/cpp/sdl3bridge/src/main.c @@ -359,8 +359,7 @@ static void refresh_status(JNIEnv *env) SDL_free(ids); snprintf(diag, sizeof(diag), "Forced SDL3 USB mode: waiting for the USB driver. Plug the controller in with a USB-C cable, " - "then tap 'Force SDL3 USB driver' and allow USB access (or switch Controller source to Auto to " - "also use Bluetooth / Android-only controllers). Ignored Android-only controllers: %d", ignored); + "then tap 'Force SDL3 USB driver' and allow USB access. Android-only controllers: %d", ignored); if (!atomic_load(&g_force)) snprintf(diag, sizeof(diag), "Waiting for a controller: plug any SDL3-supported controller in via USB-C " "(full features) or connect it over Bluetooth"); diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SdlBridgeSettings.kt b/app/src/main/java/com/droiddeck/launcher/ui/SdlBridgeSettings.kt index 59381bee0..36f67c02e 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SdlBridgeSettings.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SdlBridgeSettings.kt @@ -236,7 +236,9 @@ internal fun SdlBridgeSettingsTab(host: MenuHost, track: (String) -> Modifier = ToggleRow(host, "sdl3-driver", "SDL3 driver", "On: SDL3 reads the controller (all its features). Off: the controller goes back to Android and DroidDeck.", s.getBoolean("driverEnabled"), chipModifier = track("sdl3-driver")) { SdlBridgeLink.set("driverEnabled", bool = it) } - BridgeChoiceRow(host, "sdl3-source", "Controller source", "Which controllers SDL3 uses.", + if (s.getBoolean("embedded")) SettingsRow("Bluetooth and built-in controllers", + "Handled by DroidDeck's Android input. SDL3 handles USB controllers.") {} + else BridgeChoiceRow(host, "sdl3-source", "Controller source", "Which controllers SDL3 uses.", listOf(false to "USB + Bluetooth (auto)", true to "USB only (forced)"), s.getBoolean("forceUsb"), chipModifier = track("sdl3-source")) { SdlBridgeLink.set("forceUsb", bool = it) } diff --git a/app/src/main/java/com/example/dsedge/BridgeActivity.java b/app/src/main/java/com/example/dsedge/BridgeActivity.java index 822836b22..1da4eda00 100644 --- a/app/src/main/java/com/example/dsedge/BridgeActivity.java +++ b/app/src/main/java/com/example/dsedge/BridgeActivity.java @@ -174,7 +174,7 @@ public class BridgeActivity extends android.app.Activity implements NativeBridge // ---- SDL3 driver ---- LinearLayout drv = card(root); cardTitle(drv, "SDL3 driver"); - addSpinner(drv, "Controller source", new String[] { + if (!Host.embedded(this)) addSpinner(drv, "Controller source", new String[] { "Auto: any controller (SDL3 USB driver when plugged in, else Android/Bluetooth)", "Forced: only SDL3's USB driver (USB cable only)" }, Settings.forceUsb ? 1 : 0, v -> { diff --git a/app/src/main/java/com/example/dsedge/BridgeService.java b/app/src/main/java/com/example/dsedge/BridgeService.java index 4e0a437cf..61165120a 100644 --- a/app/src/main/java/com/example/dsedge/BridgeService.java +++ b/app/src/main/java/com/example/dsedge/BridgeService.java @@ -832,6 +832,7 @@ public class BridgeService extends Service implements NativeBridge.Listener { b.putInt("presentAs", Settings.presentAs); b.putBoolean("driverEnabled", Settings.driverEnabled); b.putBoolean("forceUsb", Settings.forceUsb); + b.putBoolean("embedded", Host.embedded(this)); b.putInt("pollMs", Settings.pollMs); b.putBoolean("bubble", Settings.bubble); // choices diff --git a/app/src/main/java/com/example/dsedge/Host.java b/app/src/main/java/com/example/dsedge/Host.java index 4f4e61bb9..db523b9ce 100644 --- a/app/src/main/java/com/example/dsedge/Host.java +++ b/app/src/main/java/com/example/dsedge/Host.java @@ -16,6 +16,11 @@ final class Host { return c != null && !OWN_PACKAGE.equals(c.getPackageName()); } + static boolean usbOnly(Context c, boolean configured) { + // Android input events go to DroidDeck's activity, outside the headless SDL process. + return embedded(c) || configured; + } + /** A preferences file name of the bridge: unchanged as its own app, prefixed when built into another app. */ static String prefs(Context c, String name) { return embedded(c) ? "sdl3bridge_" + name : name; diff --git a/app/src/main/java/com/example/dsedge/NativeBridge.java b/app/src/main/java/com/example/dsedge/NativeBridge.java index e5ee0bf30..8b21fa27a 100644 --- a/app/src/main/java/com/example/dsedge/NativeBridge.java +++ b/app/src/main/java/com/example/dsedge/NativeBridge.java @@ -305,7 +305,7 @@ public final class NativeBridge { /** Forced mode: ignore Android's basic input and only use SDL's own USB driver. */ static void applyForce() { - try { nativeSetForce(Settings.forceUsb ? 1 : 0); } catch (UnsatisfiedLinkError e) { } + try { nativeSetForce(Host.usbOnly(appContext, Settings.forceUsb) ? 1 : 0); } catch (UnsatisfiedLinkError e) { } } static void rescan() { @@ -321,7 +321,7 @@ public final class NativeBridge { try { nativeSetDriver(Settings.driverEnabled ? 1 : 0); nativeSetHookup(hookupMode()); - if (Settings.driverEnabled) nativeSetForce(Settings.forceUsb ? 1 : 0); + if (Settings.driverEnabled) nativeSetForce(Host.usbOnly(appContext, Settings.forceUsb) ? 1 : 0); } catch (UnsatisfiedLinkError e) { } } diff --git a/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt b/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt index e5442d08f..93ed5682d 100644 --- a/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt +++ b/app/src/test/java/com/example/dsedge/EmbeddedBridgeTest.kt @@ -2,6 +2,7 @@ package com.example.dsedge import android.app.Application import android.content.ComponentName +import android.content.ContextWrapper import android.content.pm.PackageManager import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -41,4 +42,16 @@ class EmbeddedBridgeTest { assertEquals("sdl3bridge_settings", Host.prefs(app, "settings")) assertEquals("sdl3bridge_engine", Host.prefs(app, "engine")) } + + @Test + fun headlessEmbeddedDriverLeavesAndroidControllersToDroidDeck() { + val app = RuntimeEnvironment.getApplication() + assertTrue(Host.usbOnly(app, false)) + assertTrue(Host.usbOnly(app, true)) + val standalone = object : ContextWrapper(app) { + override fun getPackageName() = Host.OWN_PACKAGE + } + assertFalse(Host.usbOnly(standalone, false)) + assertTrue(Host.usbOnly(standalone, true)) + } } diff --git a/docs/development/sdl3-controllers.md b/docs/development/sdl3-controllers.md index 014348631..7b16517e3 100644 --- a/docs/development/sdl3-controllers.md +++ b/docs/development/sdl3-controllers.md @@ -3,7 +3,9 @@ The integrated bridge runs in DroidDeck's `:sdl3` process. Open **Settings → SDL3Bridge** or the SDL3Bridge page in the session drawer to configure up to four controllers. USB controllers use SDL3's HID drivers; Android input remains the fallback for -Bluetooth and built-in controllers. Steam Native Hook-up forwards a USB controller's +Bluetooth and built-in controllers. The embedded driver only claims USB controllers: +Android events are delivered to DroidDeck's activity and cannot reach the separate +headless SDL process. Steam Native Hook-up forwards a USB controller's identity and reports to Steam so Steam can use its own controller driver. USB access requires Android's device permission. The optional floating control also