From c86a641e05dce7df38f5990d00eece44a973eb19 Mon Sep 17 00:00:00 2001 From: NickVs2015 Date: Tue, 3 Mar 2026 10:14:51 +0300 Subject: [PATCH] fix: add suppord android 9 gamepad and remote control (#2302) --- .../src/org/amnezia/vpn/AmneziaActivity.kt | 139 +++++++++++------- 1 file changed, 82 insertions(+), 57 deletions(-) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 293f21eaf..e8613d580 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -92,7 +92,7 @@ class AmneziaActivity : QtActivity() { private val actionResultHandlers = mutableMapOf() private val permissionRequestHandlers = mutableMapOf() - + private var isActivityResumed = false private var hasWindowFocus = false private val resumeHandler = Handler(Looper.getMainLooper()) @@ -295,7 +295,7 @@ class AmneziaActivity : QtActivity() { super.onWindowFocusChanged(hasFocus) hasWindowFocus = hasFocus Log.d(TAG, "Window focus changed: hasFocus=$hasFocus") - + // Cancel pending operations if window loses focus if (!hasFocus) { resumeHandler.removeCallbacksAndMessages(null) @@ -309,68 +309,93 @@ class AmneziaActivity : QtActivity() { val source = event.source if (deviceId < 0 && pressed) { - when (keyCode) { - KeyEvent.KEYCODE_BUTTON_A, - KeyEvent.KEYCODE_BUTTON_B, - KeyEvent.KEYCODE_BUTTON_X, - KeyEvent.KEYCODE_BUTTON_Y, - KeyEvent.KEYCODE_BUTTON_START, - KeyEvent.KEYCODE_BUTTON_SELECT -> { - nativeGamepadKeyEvent(0, keyCode, true) - nativeGamepadKeyEvent(0, keyCode, false) - return true - } - KeyEvent.KEYCODE_DPAD_CENTER -> { - if (isOnTv()) { - val down = KeyEvent( - event.downTime, - event.eventTime, - KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_ENTER, - 0, - event.metaState, - 0, - event.scanCode, - event.flags, - event.source - ) - val up = KeyEvent( - event.downTime, - event.eventTime, - KeyEvent.ACTION_UP, - KeyEvent.KEYCODE_ENTER, - 0, - event.metaState, - 0, - event.scanCode, - event.flags, - event.source - ) - super.dispatchKeyEvent(down) - super.dispatchKeyEvent(up) - return true - } - nativeGamepadKeyEvent(0, keyCode, true) - nativeGamepadKeyEvent(0, keyCode, false) - return true - } - } + when (keyCode) { + KeyEvent.KEYCODE_BUTTON_A, + KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BUTTON_X, + KeyEvent.KEYCODE_BUTTON_Y, + KeyEvent.KEYCODE_BUTTON_START, + KeyEvent.KEYCODE_BUTTON_SELECT -> { + nativeGamepadKeyEvent(0, keyCode, true) + nativeGamepadKeyEvent(0, keyCode, false) + return true + } + KeyEvent.KEYCODE_DPAD_CENTER -> { + if (isOnTv()) { + val down = KeyEvent( + event.downTime, + event.eventTime, + KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_ENTER, + 0, + event.metaState, + 0, + event.scanCode, + event.flags, + event.source + ) + val up = KeyEvent( + event.downTime, + event.eventTime, + KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_ENTER, + 0, + event.metaState, + 0, + event.scanCode, + event.flags, + event.source + ) + super.dispatchKeyEvent(down) + super.dispatchKeyEvent(up) + return true + } + nativeGamepadKeyEvent(0, keyCode, true) + nativeGamepadKeyEvent(0, keyCode, false) + return true + } + } } - // Real gamepad events (deviceId >= 0) + // Real devices (remotes and gamepads) have deviceId >= 0. if (deviceId >= 0) { val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD + if (isGamepad || isJoystick || isDpad) { - nativeGamepadKeyEvent(deviceId, keyCode, pressed) - return true + when (keyCode) { + KeyEvent.KEYCODE_BUTTON_A, + KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BUTTON_X, + KeyEvent.KEYCODE_BUTTON_Y, + KeyEvent.KEYCODE_BUTTON_START, + KeyEvent.KEYCODE_BUTTON_SELECT, + KeyEvent.KEYCODE_DPAD_CENTER -> { + nativeGamepadKeyEvent(0, keyCode, pressed) + return true + } + KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.KEYCODE_DPAD_RIGHT -> { + val synthetic = KeyEvent( + event.downTime, event.eventTime, event.action, event.keyCode, + event.repeatCount, event.metaState, -1, event.scanCode, + event.flags, InputDevice.SOURCE_KEYBOARD + ) + return super.dispatchKeyEvent(synthetic) + } + } + return super.dispatchKeyEvent(event) } } return super.dispatchKeyEvent(event) } + + private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean) override fun onPause() { @@ -412,13 +437,13 @@ class AmneziaActivity : QtActivity() { sendTouch(1f, 1f) } }, 100) - + resumeHandler.postDelayed({ if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) { sendTouch(2f, 2f) } }, 200) - + resumeHandler.postDelayed({ if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) { requestLayout() @@ -464,25 +489,25 @@ class AmneziaActivity : QtActivity() { ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets -> val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime()) val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime()) - + val imeHeight = if (imeVisible) imeInsets.bottom else 0 val density = resources.displayMetrics.density val imeHeightDp = (imeHeight / density).toInt() - + // Also track system bars (navigation bar, status bar) changes val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val navBarHeight = systemBarsInsets.bottom val navBarHeightDp = (navBarHeight / density).toInt() val statusBarHeight = systemBarsInsets.top val statusBarHeightDp = (statusBarHeight / density).toInt() - + mainScope.launch { qtInitialized.await() QtAndroidController.onImeInsetsChanged(imeHeightDp) QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp) } - + // Return windowInsets instead of CONSUMED to allow proper handling windowInsets }