From cd33858020567ed6ee4d9aa47db694bbfeb04752 Mon Sep 17 00:00:00 2001 From: Marcus Pereira Date: Tue, 28 Jul 2026 23:08:22 -0300 Subject: [PATCH] Respect the device rotation lock on Android The manifest asks for android:screenOrientation="fullUser", but SDL overrides it at window creation: SDLActivity.setOrientationBis, given a resizable window and no SDL_HINT_ORIENTATIONS -- which is what conf.lua produces on Android -- requests SCREEN_ORIENTATION_FULL_SENSOR. The *_SENSOR constants follow the accelerometer even when the player has turned auto-rotate off, so the game rotated anyway on a locked device. Override setOrientationBis in GameActivity to remap SDL's request onto the matching *_USER constant after super has run. The same orientations stay allowed and SDL keeps deciding which ones those are; only the tie-break changes, from the sensor to the system rotation setting. --- conf.lua | 5 +- mobile/ANDROID.md | 2 +- .../java/org/love2d/android/GameActivity.java | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/conf.lua b/conf.lua index 04dd2997..28e16776 100644 --- a/conf.lua +++ b/conf.lua @@ -54,7 +54,10 @@ function love.conf(t) -- the game follow the device. The renderer letterboxes the 160x144 -- viewport into whatever size results, and the on-screen touch controls -- re-lay themselves out from the new window size, so both orientations - -- just work. iOS follows the Info.plist orientations + -- just work. FULL_SENSOR ignores the device's rotation lock, so + -- GameActivity.setOrientationBis remaps it to FULL_USER after SDL has + -- run: same orientations allowed, but auto-rotate being off now wins. + -- iOS follows the Info.plist orientations -- (see mobile/ios/overlays/love-ios.plist, now portrait + landscape). t.window.resizable = true -- Starting size is a tall portrait hint; the OS resizes to the real diff --git a/mobile/ANDROID.md b/mobile/ANDROID.md index 1d4d6849..3c88c3f8 100644 --- a/mobile/ANDROID.md +++ b/mobile/ANDROID.md @@ -79,7 +79,7 @@ scripts, tests, and mobile build sources are excluded. | --- | --- | | `app.application_id` | `com.theboisclub.pokemonred` | | `app.name` | Pokemon Red | -| `app.orientation` | `portrait` | +| `app.orientation` | `fullUser`. This is only the manifest default: SDL requests FULL_SENSOR at window creation (resizable window, no `SDL_HINT_ORIENTATIONS`), and `GameActivity.setOrientationBis` remaps that to FULL_USER so the device's rotation lock is honoured. | | `app.version_name` / `app.version_code` | set from `--version X.Y.Z` (code = major*10000 + minor*100 + patch); left as-is if `--version` is omitted | | Permissions | INTERNET / RECORD_AUDIO / WRITE_EXTERNAL_STORAGE stripped; VIBRATE + BLUETOOTH kept | diff --git a/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java b/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java index 96f4aee5..db4405d3 100644 --- a/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java +++ b/mobile/android/love/src/main/java/org/love2d/android/GameActivity.java @@ -41,6 +41,7 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.AssetManager; import android.media.AudioManager; @@ -299,6 +300,54 @@ public class GameActivity extends SDLActivity { super.onResume(); } + /** + * SDL decides the activity's requested orientation at window creation + * (SDLActivity.setOrientationBis). With a resizable window and no + * SDL_HINT_ORIENTATIONS -- exactly what conf.lua produces on Android -- + * it asks for SCREEN_ORIENTATION_FULL_SENSOR, and that request overrides + * the android:screenOrientation="fullUser" set in the manifest. The + * *_SENSOR constants follow the accelerometer even when the player has + * turned auto-rotate off, so the game kept rotating on a device whose + * rotation was locked. + * + * Remap SDL's choice onto the matching *_USER constant, which allows the + * same orientations but defers to the system rotation setting. Applied + * after super so SDL keeps deciding *which* orientations the window may + * take; this only changes who breaks the tie, the sensor or the player. + */ + @Override + public void setOrientationBis(int w, int h, boolean resizable, String hint) { + super.setOrientationBis(w, h, resizable, hint); + + // The *_USER constants only exist from API 18; below that the sensor + // ones are all there is, so leave SDL's request alone. + if (android.os.Build.VERSION.SDK_INT < 18) { + return; + } + + int requested = getRequestedOrientation(); + int userRequested; + switch (requested) { + case ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR: + userRequested = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; + break; + case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE: + userRequested = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; + break; + case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT: + userRequested = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; + break; + default: + // SENSOR / plain LANDSCAPE / PORTRAIT etc: either already + // explicit or never produced by setOrientationBis. + return; + } + + Log.d("GameActivity", "requestedOrientation " + requested + " -> " + userRequested + + " (honour the device rotation lock)"); + setRequestedOrientation(userRequested); + } + @Keep public void setImmersiveMode(boolean immersive_mode) { if (android.os.Build.VERSION.SDK_INT >= 28) {