diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt index 14bb5852f..730b484f0 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/GameHost.kt @@ -40,6 +40,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su // last known Android rotation (0,1,2,3) private var lastRotation: Int? = null + // Debounce for resize kick + private var lastKickAt = 0L + var currentSurface: Long = -1 private set @@ -52,6 +55,11 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su mainViewModel.gameHost = this } + private fun ghLog(msg: String) { + val enabled = BuildConfig.DEBUG && org.kenjinx.android.viewmodels.QuickSettings(mainViewModel.activity).enableDebugLogs + if (enabled) Log.d("GameHost", msg) + } + override fun surfaceCreated(holder: SurfaceHolder) { // no-op } @@ -112,10 +120,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su val id = mainViewModel.physicalControllerManager?.connect() mainViewModel.motionSensorManager?.setControllerId(id ?: -1) - // ❌ Removed: initial "flip" at 270° (caused 90° lock at start right) - // NativeHelpers.instance.setIsInitialOrientationFlipped(mainViewModel.activity.display?.rotation == 3) - - // ✅ Correct: Report current Android rotation directly to the native site + // No initial "flip" special case: we give the real rotation downwards val currentRot = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { mainViewModel.activity.display?.rotation } else { @@ -126,14 +131,12 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su KenjinxNative.setSurfaceRotationByAndroidRotation(currentRot) // Pass the window handle for safety reasons (if Surface has just been refreshed) try { KenjinxNative.deviceSetWindowHandle(currentWindowHandle) } catch (_: Throwable) {} - // Swapchain/Viewport “knock”: set identical size again + // gentle kick: set identical size again if (width > 0 && height > 0) { try { KenjinxNative.resizeRendererAndInput(width, height) } catch (_: Throwable) {} } } catch (_: Throwable) {} - // NO graphicsRendererSetSize here – we set it via the stabilizer! - _guestThread = thread(start = true, name = "KenjinxGuest") { runGame() } @@ -192,7 +195,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su if (_isClosed) return if (w <= 0 || h <= 0) return try { - Log.d("GameHost", "safeSetSize: ${w}x$h (started=$_isStarted)") + ghLog("safeSetSize: ${w}x$h (started=$_isStarted)") KenjinxNative.graphicsRendererSetSize(w, h) if (_isStarted) { KenjinxNative.inputSetClientSize(w, h) @@ -203,8 +206,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su } /** - * Called on the activity when the rotation/layout changes. - * Detects 90°↔270° and immediately forces a NativeWindow query. + * Called by the activity when the rotation/layout changes. + * Detects 90°↔270° and forces (debounces) a requery/resize. */ fun onOrientationOrSizeChanged(rotation: Int? = null) { if (_isClosed) return @@ -225,11 +228,15 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su try { KenjinxNative.deviceSetWindowHandle(currentWindowHandle) } catch (_: Throwable) {} } catch (_: Throwable) {} - // 3) Swapchain/Viewport directly “knock”, set identical size again - val w = if (holder.surfaceFrame.width() > 0) holder.surfaceFrame.width() else width - val h = if (holder.surfaceFrame.height() > 0) holder.surfaceFrame.height() else height - if (w > 0 && h > 0) { - try { KenjinxNative.resizeRendererAndInput(w, h) } catch (_: Throwable) {} + // 3) Debounced kick of identical size (update swap chain/viewport) + val now = android.os.SystemClock.uptimeMillis() + if (now - lastKickAt >= 300L) { + lastKickAt = now + val w = if (holder.surfaceFrame.width() > 0) holder.surfaceFrame.width() else width + val h = if (holder.surfaceFrame.height() > 0) holder.surfaceFrame.height() else height + if (w > 0 && h > 0) { + try { KenjinxNative.resizeRendererAndInput(w, h) } catch (_: Throwable) {} + } } } @@ -273,7 +280,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su // If rotation is known: Force plausibility (Landscape ↔ Portrait) expectedRotation?.let { rot -> - // ROTATION_90 (1) / ROTATION_270 (3) => Landscape val landscape = (rot == 1 || rot == 3) if (landscape && h > w) { val t = w; w = h; h = t @@ -293,9 +299,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su attempts++ - // 2 consecutive identical measurements OR 20 attempts → apply - if ((stableCount >= 2 || attempts >= 20) && w > 0 && h > 0) { - Log.d("GameHost", "resize stabilized after $attempts ticks → ${w}x$h") + // slightly tightened: 1 stable tick or max. 12 attempts + if ((stableCount >= 1 || attempts >= 12) && w > 0 && h > 0) { + ghLog("resize stabilized after $attempts ticks → ${w}x$h") safeSetSize(w, h) stabilizerActive = false return diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt index bf9ecfbdf..c14e4f145 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt @@ -10,7 +10,6 @@ import android.content.Intent import android.os.Build import android.os.Handler import android.os.Looper -import android.net.Uri import android.util.Log import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize @@ -22,7 +21,6 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.documentfile.provider.DocumentFile -import androidx.preference.PreferenceManager import com.anggrayudi.storage.SimpleStorageHelper import com.sun.jna.JNIEnv import org.kenjinx.android.ui.theme.KenjinxAndroidTheme @@ -30,8 +28,6 @@ import org.kenjinx.android.viewmodels.MainViewModel import org.kenjinx.android.viewmodels.QuickSettings import org.kenjinx.android.viewmodels.GameModel import org.kenjinx.android.views.MainView -import java.io.File -import android.content.res.Configuration import android.content.Context import android.content.pm.ActivityInfo import android.hardware.display.DisplayManager @@ -54,9 +50,15 @@ class MainActivity : BaseActivity() { private lateinit var displayManager: DisplayManager private var lastKnownRotation: Int? = null private var pulsingOrientation = false + private var lastPulseAt = 0L private val TAG_ROT = "RotationDebug" + private fun rotLog(msg: String) { + val enabled = BuildConfig.DEBUG && QuickSettings(this).enableDebugLogs + if (enabled) Log.d(TAG_ROT, msg) + } + private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayAdded(displayId: Int) {} override fun onDisplayRemoved(displayId: Int) {} @@ -70,23 +72,23 @@ class MainActivity : BaseActivity() { val rot = display?.rotation if (rot == lastKnownRotation) return - Log.d(TAG_ROT, "onDisplayChanged: display.rotation=$rot → ${deg(rot)}°") + rotLog("onDisplayChanged: display.rotation=$rot → ${deg(rot)}°") val pref = QuickSettings(this@MainActivity).orientationPreference val old = lastKnownRotation lastKnownRotation = rot - // 1) Inform Native/Renderer + // 1) Inform Native/Renderer (applies to Sensor & SensorLandscape) try { KenjinxNative.setSurfaceRotationByAndroidRotation(rot) } catch (_: Throwable) {} - // 2) Initiate host resize + // 2) Initiate host resize (applies to Sensor & SensorLandscape) if (isGameRunning) { handler.post { try { mainViewModel?.gameHost?.onOrientationOrSizeChanged(rot) } catch (_: Throwable) {} } } - // 3) For SENSOR_LANDSCAPE possibly pulse, if 90↔270 flip + // 3) Only with SENSOR_LANDSCAPE: gentle pulse with real 90↔270 flip if (pref == QuickSettings.OrientationPreference.SensorLandscape && old != null && rot != null) { val isSideFlip = (old == Surface.ROTATION_90 && rot == Surface.ROTATION_270) || (old == Surface.ROTATION_270 && rot == Surface.ROTATION_90) @@ -104,10 +106,12 @@ class MainActivity : BaseActivity() { } private fun doOrientationPulse(currentRot: Int) { - if (pulsingOrientation) return + val now = android.os.SystemClock.uptimeMillis() + if (pulsingOrientation || now - lastPulseAt < 350L) return pulsingOrientation = true + lastPulseAt = now - // Short lock on the target page (instead of portrait intermediate step; prevents flickering) + // Short lock on the target page (prevents flickering) val lock = if (currentRot == Surface.ROTATION_90) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else @@ -249,7 +253,7 @@ class MainActivity : BaseActivity() { storedIntent = intent if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Log.d(TAG_ROT, "onCreate: initial display.rotation=${display?.rotation} → ${deg(display?.rotation)}°") + rotLog("onCreate: initial display.rotation=${display?.rotation} → ${deg(display?.rotation)}°") } } @@ -293,7 +297,7 @@ class MainActivity : BaseActivity() { // Enable display listener if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { lastKnownRotation = display?.rotation - Log.d(TAG_ROT, "onResume: display.rotation=${display?.rotation} → ${deg(display?.rotation)}°") + rotLog("onResume: display.rotation=${display?.rotation} → ${deg(display?.rotation)}°") } try { displayManager.registerDisplayListener(displayListener, handler) } catch (_: Throwable) {} @@ -340,7 +344,7 @@ class MainActivity : BaseActivity() { } else { TODO("VERSION.SDK_INT < R") } - Log.d(TAG_ROT, "applyOrientationPreference: rot=$rot → ${deg(rot)}°, pref=${pref.name}") + rotLog("applyOrientationPreference: rot=$rot → ${deg(rot)}°, pref=${pref.name}") try { KenjinxNative.setSurfaceRotationByAndroidRotation(rot) } catch (_: Throwable) {} }