Maelstrom: Build Maelstrom with SDL 3.4.4

From f327431bd4158f415af40792ab45e91099f0cab9 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 29 Mar 2026 08:27:07 -0700
Subject: [PATCH] Build Maelstrom with SDL 3.4.4

---
 .../app/HIDDeviceBLESteamController.java      | 128 +++---------------
 .../java/org/libsdl/app/HIDDeviceManager.java |   8 +-
 .../main/java/org/libsdl/app/SDLActivity.java |   4 +-
 external/SDL                                  |   2 +-
 game/game.cpp                                 |  13 ++
 5 files changed, 33 insertions(+), 122 deletions(-)

diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java
index 97b4c127..bf1ca214 100644
--- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java
+++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java
@@ -19,13 +19,9 @@
 
 import java.lang.Runnable;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.UUID;
 
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
 class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
 
     private static final String TAG = "hidapi";
@@ -41,11 +37,6 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
     private LinkedList<GattOperation> mOperations;
     GattOperation mCurrentOperation = null;
     private Handler mHandler;
-    private int mProductId = -1;
-
-    private static final int D0G_BLE2_PID = 0x1106;
-    private static final int TRITON_BLE_PID = 0x1303;
-
 
     private static final int TRANSPORT_AUTO = 0;
     private static final int TRANSPORT_BREDR = 1;
@@ -54,13 +45,10 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
     private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
 
     static final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
-    static final UUID inputCharacteristicD0G = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
-    static final UUID inputCharacteristicTriton = UUID.fromString("100F6C7A-1735-4313-B402-38567131E5F3");
+    static final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
     static final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
     static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
 
-    private HashMap<Integer, BluetoothGattCharacteristic> mOutputReportChars = new HashMap<Integer, BluetoothGattCharacteristic>();
-
     static class GattOperation {
         private enum Operation {
             CHR_READ,
@@ -326,38 +314,8 @@ private boolean probeService(HIDDeviceBLESteamController controller) {
                 Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
 
                 for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
-                    boolean bShouldStartNotifications = false;
-
-                    if (chr.getUuid().equals(inputCharacteristicTriton)) {
-                        Log.v(TAG, "Found Triton input characteristic");
-                        mProductId = TRITON_BLE_PID;
-                        bShouldStartNotifications = true;
-                    } else if (chr.getUuid().equals(inputCharacteristicD0G)) {
-                        Log.v(TAG, "Found D0G input characteristic");
-                        mProductId = D0G_BLE2_PID;
-                        bShouldStartNotifications = true;
-                    } else {
-                        Pattern reportPattern = Pattern.compile("100F6C([0-9A-Z]{2})", Pattern.CASE_INSENSITIVE);
-                        Matcher matcher = reportPattern.matcher(chr.getUuid().toString());
-
-                        if (matcher.find()) {
-                            try {
-                                int reportId = Integer.parseInt(matcher.group(1), 16);
-
-                                reportId -= 0x35;
-                                if (reportId >= 0x80) {
-                                    // This is a Triton output report characteristic that we need to care about.
-                                    Log.v(TAG, "Found Triton output report 0x" + Integer.toString(reportId, 16));
-                                    mOutputReportChars.put(reportId, chr);
-                                }
-                            }
-                            catch (NumberFormatException nfe) {
-                                Log.w(TAG, "Could not parse report characteristic " + chr.getUuid().toString() + ": " + nfe.toString());
-                            }
-                        }
-                    }
-
-                    if (bShouldStartNotifications) {
+                    if (chr.getUuid().equals(inputCharacteristic)) {
+                        Log.v(TAG, "Found input characteristic");
                         // Start notifications
                         BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
                         if (cccd != null) {
@@ -490,16 +448,8 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                 mIsConnected = false;
                 gatt.disconnect();
                 mGatt = connectGatt(false);
-            } else {
-                if (getProductId() == TRITON_BLE_PID) {
-                    // Android will not properly play well with Data Length Extensions without manually requesting a large MTU,
-                    // and Triton controllers require DLE support.
-                    //
-                    // 517 is basically a "magic number" as far as Android's bluetooth code is concerned, so do not change
-                    // this value. It is functionally "please enable data length extensions" on some Android builds.
-                    mGatt.requestMtu(517);
-                }
-
+            }
+            else {
                 probeService(this);
             }
         }
@@ -537,7 +487,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris
     // Enable this for verbose logging of controller input reports
         //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
 
-        if (characteristic.getUuid().equals(getInputCharacteristic()) && !mFrozen) {
+        if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
             mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
         }
     }
@@ -552,21 +502,13 @@ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descri
         BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
         //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
 
-        if (chr.getUuid().equals(getInputCharacteristic())) {
+        if (chr.getUuid().equals(inputCharacteristic)) {
             boolean hasWrittenInputDescriptor = true;
             BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
             if (reportChr != null) {
-                if (getProductId() == TRITON_BLE_PID) {
-                    // For Triton we just mark things registered.
-                    Log.v(TAG, "Registering Triton Steam Controller with ID: " + getId());
-                    mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0, true);
-                    setRegistered();
-                } else {
-                    // For the original controller, we need to manually enter Valve mode.
-                    Log.v(TAG, "Writing report characteristic to enter valve mode");
-                    reportChr.setValue(enterValveMode);
-                    gatt.writeCharacteristic(reportChr);
-                }
+                Log.v(TAG, "Writing report characteristic to enter valve mode");
+                reportChr.setValue(enterValveMode);
+                gatt.writeCharacteristic(reportChr);
             }
         }
 
@@ -606,28 +548,9 @@ public int getVendorId() {
 
     @Override
     public int getProductId() {
-        if (mProductId > 0) {
-            // We've already set a product ID.
-            return mProductId;
-        }
-
-        if (mDevice.getName().startsWith("Steam Ctrl")) {
-            // We're a newer Triton device
-            mProductId = TRITON_BLE_PID;
-        } else {
-            // We're an OG Steam Controller
-            mProductId = D0G_BLE2_PID;
-        }
-
-        return mProductId;
-    }
-
-    private UUID getInputCharacteristic() {
-        if (getProductId() == TRITON_BLE_PID) {
-            return inputCharacteristicTriton;
-        } else {
-            return inputCharacteristicD0G;
-        }
+        // We don't have an easy way to query from the Bluetooth device, but we know what it is
+        final int D0G_BLE2_PID = 0x1106;
+        return D0G_BLE2_PID;
     }
 
     @Override
@@ -678,29 +601,10 @@ public int writeReport(byte[] report, boolean feature) {
             writeCharacteristic(reportCharacteristic, actual_report);
             return report.length;
         } else {
-            // If we're an original-recipe Steam Controller we just write to the characteristic directly.
-            if (getProductId() == D0G_BLE2_PID) {
-                //Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
-                writeCharacteristic(reportCharacteristic, report);
-                return report.length;                
-            }
-
-            // If we're a Triton, we need to find the correct report characteristic.
-            if (report.length > 0) {
-                int reportId = report[0];
-                BluetoothGattCharacteristic targetedReportCharacteristic = mOutputReportChars.get(reportId);
-                if (targetedReportCharacteristic != null) {
-                    byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
-                    //Log.v(TAG, "writeOutputReport 0x" + Integer.toString(reportId, 16) + " " + HexDump.dumpHexString(report));
-                    writeCharacteristic(targetedReportCharacteristic.getUuid(), actual_report);
-                    return report.length;
-                } else {
-                    Log.w(TAG, "Got report write request for unknown report type 0x" + Integer.toString(reportId, 16));
-                }
-            }
+            //Log.v(TAG, "writeOutputReport " + HexDump.dumpHexString(report));
+            writeCharacteristic(reportCharacteristic, report);
+            return report.length;
         }
-
-        return -1;
     }
 
     @Override
diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
index b00c905d..1fb2bfb4 100644
--- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
+++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
@@ -529,13 +529,7 @@ boolean isSteamController(BluetoothDevice bluetoothDevice) {
             return false;
         }
 
-        // Steam Controllers will always support Bluetooth Low Energy
-        if ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) == 0) {
-            return false;
-        }
-
-        // Match on the name either the original Steam Controller or the new second-generation one advertise with.
-        return bluetoothDevice.getName().equals("SteamController") || bluetoothDevice.getName().startsWith("Steam Ctrl");
+        return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
     }
 
     private void close() {
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index dcc61d87..0ca7b118 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -60,8 +60,8 @@
 public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {
     private static final String TAG = "SDL";
     private static final int SDL_MAJOR_VERSION = 3;
-    private static final int SDL_MINOR_VERSION = 5;
-    private static final int SDL_MICRO_VERSION = 0;
+    private static final int SDL_MINOR_VERSION = 4;
+    private static final int SDL_MICRO_VERSION = 3;
 /*
     // Display InputType.SOURCE/CLASS of events and devices
     //
diff --git a/external/SDL b/external/SDL
index 122ad3d6..1fc5001f 160000
--- a/external/SDL
+++ b/external/SDL
@@ -1 +1 @@
-Subproject commit 122ad3d6f600229dbb4f52883a907699cd3a1acd
+Subproject commit 1fc5001f77d50819c66bbe87b7e266fee247973f
diff --git a/game/game.cpp b/game/game.cpp
index 27e5b478..22ecdafc 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -583,6 +583,19 @@ GamePanelDelegate::OnAction(UIBaseElement *sender, const char *action)
 	return true;
 }
 
+#if !SDL_VERSION_ATLEAST(3, 5, 0)
+static bool SDL_IsPhone(void)
+{
+#if defined(SDL_PLATFORM_ANDROID) || \
+    (defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_VISIONOS))
+    if (!SDL_IsTablet() && !SDL_IsTV()) {
+        return true;
+    }
+#endif
+    return false;
+}
+#endif // SDL < 3.5.0
+
 void
 GamePanelDelegate::UpdateZoom()
 {