From 96832a7000af8d92717f1f69b06772b5debb41f6 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 12:50:30 +0100 Subject: [PATCH 01/16] basic controller support --- .../geode/launcher/GeometryDashActivity.kt | 99 +++++++++++++++++++ .../com/geode/launcher/utils/GeodeUtils.kt | 10 ++ app/src/main/res/values/strings.xml | 2 +- 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 0d9bb12e..9673eeeb 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -11,6 +11,9 @@ import android.os.Bundle import android.os.Environment import android.text.InputType import android.util.Log +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout @@ -64,6 +67,10 @@ fun ratioForPreference(value: String) = when (value) { else -> 1.77f } +// TODO: https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input +// our activity shit +// dispatchGenericMotionEvent and dispatchKeyEvent + class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperListener, GeodeUtils.CapabilityListener { private var mGLSurfaceView: Cocos2dxGLSurfaceView? = null private var mIsRunning = false @@ -77,6 +84,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL private var mScreenZoom = 1.0f private var mScreenZoomFit = false + private var mGamepad = Gamepad() + override fun onCreate(savedInstanceState: Bundle?) { setupUIState() FMOD.init(this) @@ -624,6 +633,96 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL } } + override fun dispatchGenericMotionEvent(event: MotionEvent?): Boolean { + // what should we do here do we just call orig with null?? + if (event == null) { + return super.dispatchGenericMotionEvent(null) + } + + if (event.source != InputDevice.SOURCE_JOYSTICK || event.action != MotionEvent.ACTION_MOVE) { + return super.dispatchGenericMotionEvent(event) + } + + fun processJoystick(event: MotionEvent, index: Int) { + if (index < 0) { + mGamepad.mJoyLeftX = event.getAxisValue(MotionEvent.AXIS_X) + mGamepad.mJoyLeftY = -event.getAxisValue(MotionEvent.AXIS_Y) + mGamepad.mJoyRightX = event.getAxisValue(MotionEvent.AXIS_Z) // wtf is axis z and rz + mGamepad.mJoyRightY = -event.getAxisValue(MotionEvent.AXIS_RZ) + } else { + mGamepad.mJoyLeftX = event.getHistoricalAxisValue(MotionEvent.AXIS_X, index) + mGamepad.mJoyLeftY = -event.getHistoricalAxisValue(MotionEvent.AXIS_Y, index) + mGamepad.mJoyRightX = event.getHistoricalAxisValue(MotionEvent.AXIS_Z, index) + mGamepad.mJoyRightY = -event.getHistoricalAxisValue(MotionEvent.AXIS_RZ, index) + } + } + + // taken from documentation - android batches joystick events for efficiency + + // Process the movements starting from the + // earliest historical position in the batch + (0 until event.historySize).forEach { i -> + // Process the event at historical position i + processJoystick(event, i) + } + + // Process the current movement sample in the batch (position -1) + processJoystick(event, -1) + + return super.dispatchGenericMotionEvent(event) + } + + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (event.source != InputDevice.SOURCE_GAMEPAD) { + return super.dispatchKeyEvent(event) + } + + return when (event.keyCode) { + KeyEvent.KEYCODE_BUTTON_A -> { mGamepad.mButtonA = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_B -> { mGamepad.mButtonB = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_X -> { mGamepad.mButtonX = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_Y -> { mGamepad.mButtonY = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_START -> { mGamepad.mButtonStart = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_SELECT -> { mGamepad.mButtonSelect = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_L1 -> { mGamepad.mButtonL = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_R1 -> { mGamepad.mButtonR = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_L2 -> { mGamepad.mButtonZL = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_R2 -> { mGamepad.mButtonZR = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_UP -> { mGamepad.mButtonUp = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_DOWN -> { mGamepad.mButtonDown = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_LEFT -> { mGamepad.mButtonLeft = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_RIGHT -> { mGamepad.mButtonRight = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_THUMBL -> { mGamepad.mButtonJoyLeft = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_THUMBR -> { mGamepad.mButtonJoyRight = event.action == KeyEvent.ACTION_DOWN; true } + else -> super.dispatchKeyEvent(event) + } + } + + fun getGamepad(): Gamepad { return mGamepad } + + class Gamepad { + var mButtonA = false + var mButtonB = false + var mButtonX = false + var mButtonY = false + var mButtonStart = false + var mButtonSelect = false + var mButtonL = false + var mButtonR = false + var mButtonZL = false + var mButtonZR = false + var mButtonUp = false + var mButtonDown = false + var mButtonLeft = false + var mButtonRight = false + var mButtonJoyLeft = false + var mButtonJoyRight = false + var mJoyLeftX = 0.0f + var mJoyLeftY = 0.0f + var mJoyRightX = 0.0f + var mJoyRightY = 0.0f + } + class EGLConfigChooser : GLSurfaceView.EGLConfigChooser { // this comes from EGL14, but is unavailable on EGL10 // also EGL14 is incompatible with EGL10. so whatever diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index c091cbe1..b1dc4125 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -26,6 +26,7 @@ import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import com.geode.launcher.BuildConfig +import com.geode.launcher.GeometryDashActivity import com.geode.launcher.R import com.geode.launcher.UserDirectoryProvider import com.geode.launcher.activityresult.GeodeOpenFileActivityResult @@ -571,6 +572,15 @@ object GeodeUtils { .launchUrl(activity, url.toUri()) } + // is this the right place to put this + @JvmStatic + fun getControllerState(): GeometryDashActivity.Gamepad? { + val act = activity.get() + return if (act is GeometryDashActivity) { + act.getGamepad() + } else null + } + external fun nativeKeyUp(keyCode: Int, modifiers: Int) external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean) external fun nativeActionScroll(scrollX: Float, scrollY: Float) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ef68555..b9afce0f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,7 +8,7 @@ Settings Download Install - Geode + balls geode logo play icon download icon From 69b9b9d613900fcf69b1979dc572776b900ce458 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 13:01:28 +0100 Subject: [PATCH 02/16] remove todo comment --- app/src/main/java/com/geode/launcher/GeometryDashActivity.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 9673eeeb..776eef84 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -67,10 +67,6 @@ fun ratioForPreference(value: String) = when (value) { else -> 1.77f } -// TODO: https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input -// our activity shit -// dispatchGenericMotionEvent and dispatchKeyEvent - class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperListener, GeodeUtils.CapabilityListener { private var mGLSurfaceView: Cocos2dxGLSurfaceView? = null private var mIsRunning = false From 7ba6549ce7b055ce10f7dee80f888ab1e2729741 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 13:05:02 +0100 Subject: [PATCH 03/16] revert testing change --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b9afce0f..2ef68555 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,7 +8,7 @@ Settings Download Install - balls + Geode geode logo play icon download icon From 1859ca89648a94d37946761e608cf8c80b68e781 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 13:46:51 +0100 Subject: [PATCH 04/16] fix d-pad and triggers --- .../geode/launcher/GeometryDashActivity.kt | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 776eef84..9bdef40c 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -11,7 +11,6 @@ import android.os.Bundle import android.os.Environment import android.text.InputType import android.util.Log -import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent import android.view.ViewGroup @@ -635,21 +634,29 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL return super.dispatchGenericMotionEvent(null) } - if (event.source != InputDevice.SOURCE_JOYSTICK || event.action != MotionEvent.ACTION_MOVE) { - return super.dispatchGenericMotionEvent(event) - } - fun processJoystick(event: MotionEvent, index: Int) { if (index < 0) { mGamepad.mJoyLeftX = event.getAxisValue(MotionEvent.AXIS_X) mGamepad.mJoyLeftY = -event.getAxisValue(MotionEvent.AXIS_Y) mGamepad.mJoyRightX = event.getAxisValue(MotionEvent.AXIS_Z) // wtf is axis z and rz mGamepad.mJoyRightY = -event.getAxisValue(MotionEvent.AXIS_RZ) + mGamepad.mTriggerZL = event.getAxisValue(MotionEvent.AXIS_LTRIGGER) + mGamepad.mTriggerZR = event.getAxisValue(MotionEvent.AXIS_RTRIGGER) + mGamepad.mButtonUp = event.getAxisValue(MotionEvent.AXIS_HAT_Y) < 0.0f + mGamepad.mButtonDown = event.getAxisValue(MotionEvent.AXIS_HAT_Y) > 0.0f + mGamepad.mButtonLeft = event.getAxisValue(MotionEvent.AXIS_HAT_X) < 0.0f + mGamepad.mButtonRight = event.getAxisValue(MotionEvent.AXIS_HAT_X) > 0.0f } else { mGamepad.mJoyLeftX = event.getHistoricalAxisValue(MotionEvent.AXIS_X, index) mGamepad.mJoyLeftY = -event.getHistoricalAxisValue(MotionEvent.AXIS_Y, index) mGamepad.mJoyRightX = event.getHistoricalAxisValue(MotionEvent.AXIS_Z, index) mGamepad.mJoyRightY = -event.getHistoricalAxisValue(MotionEvent.AXIS_RZ, index) + mGamepad.mTriggerZL = event.getHistoricalAxisValue(MotionEvent.AXIS_LTRIGGER, index) + mGamepad.mTriggerZR = event.getHistoricalAxisValue(MotionEvent.AXIS_RTRIGGER, index) + mGamepad.mButtonUp = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, index) < 0.0f + mGamepad.mButtonDown = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, index) > 0.0f + mGamepad.mButtonLeft = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, index) < 0.0f + mGamepad.mButtonRight = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, index) > 0.0f } } @@ -665,14 +672,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL // Process the current movement sample in the batch (position -1) processJoystick(event, -1) - return super.dispatchGenericMotionEvent(event) + return true; } override fun dispatchKeyEvent(event: KeyEvent): Boolean { - if (event.source != InputDevice.SOURCE_GAMEPAD) { - return super.dispatchKeyEvent(event) - } - return when (event.keyCode) { KeyEvent.KEYCODE_BUTTON_A -> { mGamepad.mButtonA = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_B -> { mGamepad.mButtonB = event.action == KeyEvent.ACTION_DOWN; true } @@ -682,8 +685,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL KeyEvent.KEYCODE_BUTTON_SELECT -> { mGamepad.mButtonSelect = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_L1 -> { mGamepad.mButtonL = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_R1 -> { mGamepad.mButtonR = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_L2 -> { mGamepad.mButtonZL = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_R2 -> { mGamepad.mButtonZR = event.action == KeyEvent.ACTION_DOWN; true } + // zl/zr/d-pad don't actually function as a button on my controllers but android documentation says to keep it in for compatibility + KeyEvent.KEYCODE_BUTTON_L2 -> { mGamepad.mTriggerZL = if (event.action == KeyEvent.ACTION_DOWN) 1.0f else 0.0f; true } + KeyEvent.KEYCODE_BUTTON_R2 -> { mGamepad.mTriggerZR = if (event.action == KeyEvent.ACTION_DOWN) 1.0f else 0.0f; true } KeyEvent.KEYCODE_DPAD_UP -> { mGamepad.mButtonUp = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_DPAD_DOWN -> { mGamepad.mButtonDown = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_DPAD_LEFT -> { mGamepad.mButtonLeft = event.action == KeyEvent.ACTION_DOWN; true } @@ -705,8 +709,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL var mButtonSelect = false var mButtonL = false var mButtonR = false - var mButtonZL = false - var mButtonZR = false + var mTriggerZL = 0.0f + var mTriggerZR = 0.0f var mButtonUp = false var mButtonDown = false var mButtonLeft = false From f26a3b98ddaea3b017a20faf99d5960612e63c39 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 19:26:58 +0100 Subject: [PATCH 05/16] vibration + colour + multiple controllers --- app/src/main/AndroidManifest.xml | 2 + .../geode/launcher/GeometryDashActivity.kt | 103 +++++++++++------- .../com/geode/launcher/utils/GeodeUtils.kt | 55 +++++++++- 3 files changed, 117 insertions(+), 43 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2606c146..6d4ad49c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,8 @@ android:glEsVersion="0x00020000" android:required="true" /> + + () override fun onCreate(savedInstanceState: Bundle?) { setupUIState() @@ -133,6 +135,19 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL } mGLSurfaceView?.manualBackEvents = true } + + // basically taken from documentation + val deviceIDs = InputDevice.getDeviceIds() + for (id in deviceIDs) { + val device = InputDevice.getDevice(id) ?: continue + + if (device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + || device.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) { + val gamepad = Gamepad() + gamepad.mDeviceID = device.id + mGamepads.add(gamepad) + } + } } private fun createVersionFile() { @@ -630,33 +645,33 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL override fun dispatchGenericMotionEvent(event: MotionEvent?): Boolean { // what should we do here do we just call orig with null?? - if (event == null) { - return super.dispatchGenericMotionEvent(null) - } + event ?: return super.dispatchGenericMotionEvent(null) fun processJoystick(event: MotionEvent, index: Int) { + val gamepad = mGamepads.firstOrNull { it.mDeviceID == event.deviceId } ?: return + if (index < 0) { - mGamepad.mJoyLeftX = event.getAxisValue(MotionEvent.AXIS_X) - mGamepad.mJoyLeftY = -event.getAxisValue(MotionEvent.AXIS_Y) - mGamepad.mJoyRightX = event.getAxisValue(MotionEvent.AXIS_Z) // wtf is axis z and rz - mGamepad.mJoyRightY = -event.getAxisValue(MotionEvent.AXIS_RZ) - mGamepad.mTriggerZL = event.getAxisValue(MotionEvent.AXIS_LTRIGGER) - mGamepad.mTriggerZR = event.getAxisValue(MotionEvent.AXIS_RTRIGGER) - mGamepad.mButtonUp = event.getAxisValue(MotionEvent.AXIS_HAT_Y) < 0.0f - mGamepad.mButtonDown = event.getAxisValue(MotionEvent.AXIS_HAT_Y) > 0.0f - mGamepad.mButtonLeft = event.getAxisValue(MotionEvent.AXIS_HAT_X) < 0.0f - mGamepad.mButtonRight = event.getAxisValue(MotionEvent.AXIS_HAT_X) > 0.0f + gamepad.mJoyLeftX = event.getAxisValue(MotionEvent.AXIS_X) + gamepad.mJoyLeftY = -event.getAxisValue(MotionEvent.AXIS_Y) + gamepad.mJoyRightX = event.getAxisValue(MotionEvent.AXIS_Z) // wtf is axis z and rz + gamepad.mJoyRightY = -event.getAxisValue(MotionEvent.AXIS_RZ) + gamepad.mTriggerZL = event.getAxisValue(MotionEvent.AXIS_LTRIGGER) + gamepad.mTriggerZR = event.getAxisValue(MotionEvent.AXIS_RTRIGGER) + gamepad.mButtonUp = event.getAxisValue(MotionEvent.AXIS_HAT_Y) < 0.0f + gamepad.mButtonDown = event.getAxisValue(MotionEvent.AXIS_HAT_Y) > 0.0f + gamepad.mButtonLeft = event.getAxisValue(MotionEvent.AXIS_HAT_X) < 0.0f + gamepad.mButtonRight = event.getAxisValue(MotionEvent.AXIS_HAT_X) > 0.0f } else { - mGamepad.mJoyLeftX = event.getHistoricalAxisValue(MotionEvent.AXIS_X, index) - mGamepad.mJoyLeftY = -event.getHistoricalAxisValue(MotionEvent.AXIS_Y, index) - mGamepad.mJoyRightX = event.getHistoricalAxisValue(MotionEvent.AXIS_Z, index) - mGamepad.mJoyRightY = -event.getHistoricalAxisValue(MotionEvent.AXIS_RZ, index) - mGamepad.mTriggerZL = event.getHistoricalAxisValue(MotionEvent.AXIS_LTRIGGER, index) - mGamepad.mTriggerZR = event.getHistoricalAxisValue(MotionEvent.AXIS_RTRIGGER, index) - mGamepad.mButtonUp = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, index) < 0.0f - mGamepad.mButtonDown = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, index) > 0.0f - mGamepad.mButtonLeft = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, index) < 0.0f - mGamepad.mButtonRight = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, index) > 0.0f + gamepad.mJoyLeftX = event.getHistoricalAxisValue(MotionEvent.AXIS_X, index) + gamepad.mJoyLeftY = -event.getHistoricalAxisValue(MotionEvent.AXIS_Y, index) + gamepad.mJoyRightX = event.getHistoricalAxisValue(MotionEvent.AXIS_Z, index) + gamepad.mJoyRightY = -event.getHistoricalAxisValue(MotionEvent.AXIS_RZ, index) + gamepad.mTriggerZL = event.getHistoricalAxisValue(MotionEvent.AXIS_LTRIGGER, index) + gamepad.mTriggerZR = event.getHistoricalAxisValue(MotionEvent.AXIS_RTRIGGER, index) + gamepad.mButtonUp = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, index) < 0.0f + gamepad.mButtonDown = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, index) > 0.0f + gamepad.mButtonLeft = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, index) < 0.0f + gamepad.mButtonRight = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, index) > 0.0f } } @@ -676,29 +691,32 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL } override fun dispatchKeyEvent(event: KeyEvent): Boolean { + val gamepad = mGamepads.firstOrNull { it.mDeviceID == event.deviceId } ?: return super.dispatchKeyEvent(event) + return when (event.keyCode) { - KeyEvent.KEYCODE_BUTTON_A -> { mGamepad.mButtonA = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_B -> { mGamepad.mButtonB = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_X -> { mGamepad.mButtonX = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_Y -> { mGamepad.mButtonY = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_START -> { mGamepad.mButtonStart = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_SELECT -> { mGamepad.mButtonSelect = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_L1 -> { mGamepad.mButtonL = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_R1 -> { mGamepad.mButtonR = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_A -> { gamepad.mButtonA = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_B -> { gamepad.mButtonB = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_X -> { gamepad.mButtonX = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_Y -> { gamepad.mButtonY = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_START -> { gamepad.mButtonStart = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_SELECT -> { gamepad.mButtonSelect = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_L1 -> { gamepad.mButtonL = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_R1 -> { gamepad.mButtonR = event.action == KeyEvent.ACTION_DOWN; true } // zl/zr/d-pad don't actually function as a button on my controllers but android documentation says to keep it in for compatibility - KeyEvent.KEYCODE_BUTTON_L2 -> { mGamepad.mTriggerZL = if (event.action == KeyEvent.ACTION_DOWN) 1.0f else 0.0f; true } - KeyEvent.KEYCODE_BUTTON_R2 -> { mGamepad.mTriggerZR = if (event.action == KeyEvent.ACTION_DOWN) 1.0f else 0.0f; true } - KeyEvent.KEYCODE_DPAD_UP -> { mGamepad.mButtonUp = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_DPAD_DOWN -> { mGamepad.mButtonDown = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_DPAD_LEFT -> { mGamepad.mButtonLeft = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_DPAD_RIGHT -> { mGamepad.mButtonRight = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_THUMBL -> { mGamepad.mButtonJoyLeft = event.action == KeyEvent.ACTION_DOWN; true } - KeyEvent.KEYCODE_BUTTON_THUMBR -> { mGamepad.mButtonJoyRight = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_L2 -> { gamepad.mTriggerZL = if (event.action == KeyEvent.ACTION_DOWN) 1.0f else 0.0f; true } + KeyEvent.KEYCODE_BUTTON_R2 -> { gamepad.mTriggerZR = if (event.action == KeyEvent.ACTION_DOWN) 1.0f else 0.0f; true } + KeyEvent.KEYCODE_DPAD_UP -> { gamepad.mButtonUp = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_DOWN -> { gamepad.mButtonDown = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_LEFT -> { gamepad.mButtonLeft = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_DPAD_RIGHT -> { gamepad.mButtonRight = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_THUMBL -> { gamepad.mButtonJoyLeft = event.action == KeyEvent.ACTION_DOWN; true } + KeyEvent.KEYCODE_BUTTON_THUMBR -> { gamepad.mButtonJoyRight = event.action == KeyEvent.ACTION_DOWN; true } else -> super.dispatchKeyEvent(event) } } - fun getGamepad(): Gamepad { return mGamepad } + fun getGamepadCount(): Int { return mGamepads.size } + fun getGamepad(id: Int): Gamepad? { return mGamepads.getOrNull(id) } class Gamepad { var mButtonA = false @@ -717,10 +735,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL var mButtonRight = false var mButtonJoyLeft = false var mButtonJoyRight = false + var mJoyLeftX = 0.0f var mJoyLeftY = 0.0f var mJoyRightX = 0.0f var mJoyRightY = 0.0f + + var mDeviceID = 0 } class EGLConfigChooser : GLSurfaceView.EGLConfigChooser { diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index b1dc4125..74383633 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -7,6 +7,8 @@ import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.hardware.lights.LightState +import android.hardware.lights.LightsRequest import android.net.Uri import android.os.Build import android.os.Environment @@ -16,6 +18,7 @@ import android.os.VibratorManager import android.provider.DocumentsContract import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION import android.util.Log +import android.view.InputDevice import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -574,13 +577,61 @@ object GeodeUtils { // is this the right place to put this @JvmStatic - fun getControllerState(): GeometryDashActivity.Gamepad? { + fun getControllerState(id: Int): GeometryDashActivity.Gamepad? { val act = activity.get() return if (act is GeometryDashActivity) { - act.getGamepad() + act.getGamepad(id) } else null } + // supports vibration or lighting + @JvmStatic + fun supportsControllerExtendedFeatures(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + + @JvmStatic + fun setControllerVibration(id: Int, duration: Long, left: Int, right: Int) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + val act = activity.get() + if (act is GeometryDashActivity) { + val gamepad = act.getGamepad(id) ?: return + val device = InputDevice.getDevice(gamepad.mDeviceID) ?: return + val manager = device.vibratorManager + val ids = manager.vibratorIds + + if (ids.size == 1) { + manager.getVibrator(ids[0]).vibrate(VibrationEffect.createOneShot(duration, (left + right) / 2)) + } + + if (ids.size == 2) { + manager.getVibrator(ids[0]).vibrate(VibrationEffect.createOneShot(duration, left)) + manager.getVibrator(ids[1]).vibrate(VibrationEffect.createOneShot(duration, right)) + } + } + } + + @JvmStatic + fun setControllerColor(id: Int, color: Int) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + val act = activity.get() + if (act is GeometryDashActivity) { + val gamepad = act.getGamepad(id) ?: return + val device = InputDevice.getDevice(gamepad.mDeviceID) ?: return + val manager = device.lightsManager + val request = LightsRequest.Builder() + for (light in manager.lights) { + request.addLight( + light, + LightState.Builder() + .setColor(color) + .build() + ) + } + manager.openSession().requestLights(request.build()) + } + } + external fun nativeKeyUp(keyCode: Int, modifiers: Int) external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean) external fun nativeActionScroll(scrollX: Float, scrollY: Float) From f2b9981805ea92ff1102d090bc65b46d16b9fbca Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 19:51:01 +0100 Subject: [PATCH 06/16] unused import --- app/src/main/java/com/geode/launcher/GeometryDashActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 73efab4a..26c93780 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -18,7 +18,6 @@ import android.view.ViewGroup import android.view.WindowManager import android.widget.FrameLayout import android.window.OnBackInvokedDispatcher -import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.compose.ui.platform.ComposeView From b45cf4683cc55815f8a5b9853000f2b0292fe662 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 20:41:11 +0100 Subject: [PATCH 07/16] add lights permission + small changes --- app/src/main/AndroidManifest.xml | 3 ++- app/src/main/java/com/geode/launcher/GeometryDashActivity.kt | 1 - app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d4ad49c..86d9b279 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ + - + Date: Tue, 3 Jun 2025 21:23:02 +0100 Subject: [PATCH 08/16] adjust comments --- app/src/main/java/com/geode/launcher/GeometryDashActivity.kt | 1 - app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 5de9ba09..8c19d7f4 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -643,7 +643,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL } override fun dispatchGenericMotionEvent(event: MotionEvent?): Boolean { - // what should we do here do we just call orig with null?? event ?: return super.dispatchGenericMotionEvent(null) fun processJoystick(event: MotionEvent, index: Int) { diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index 5c60141f..88127892 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -575,7 +575,6 @@ object GeodeUtils { .launchUrl(activity, url.toUri()) } - // is this the right place to put this @JvmStatic fun getControllerState(id: Int): GeometryDashActivity.Gamepad? { val act = activity.get() @@ -584,7 +583,7 @@ object GeodeUtils { } else null } - // supports vibration or lighting + // whether this controller supports vibration or lighting @JvmStatic fun supportsControllerExtendedFeatures(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S From 64444903ddacbab6eb6fc84994c5f89992b8c0ab Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 3 Jun 2025 21:40:07 +0100 Subject: [PATCH 09/16] be more kotlin-y --- .../geode/launcher/GeometryDashActivity.kt | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 8c19d7f4..f0396a13 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -142,9 +142,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL if (device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || device.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) { - val gamepad = Gamepad() - gamepad.mDeviceID = device.id - mGamepads.add(gamepad) + mGamepads.add(Gamepad().apply { + mDeviceID = device.id + }) } } } @@ -715,31 +715,31 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL fun getGamepad(id: Int): Gamepad? { return mGamepads.getOrNull(id) } - class Gamepad { - var mButtonA = false - var mButtonB = false - var mButtonX = false - var mButtonY = false - var mButtonStart = false - var mButtonSelect = false - var mButtonL = false - var mButtonR = false - var mTriggerZL = 0.0f - var mTriggerZR = 0.0f - var mButtonUp = false - var mButtonDown = false - var mButtonLeft = false - var mButtonRight = false - var mButtonJoyLeft = false - var mButtonJoyRight = false - - var mJoyLeftX = 0.0f - var mJoyLeftY = 0.0f - var mJoyRightX = 0.0f - var mJoyRightY = 0.0f - - var mDeviceID = 0 - } + data class Gamepad( + var mButtonA: Boolean = false, + var mButtonB: Boolean = false, + var mButtonX: Boolean = false, + var mButtonY: Boolean = false, + var mButtonStart: Boolean = false, + var mButtonSelect: Boolean = false, + var mButtonL: Boolean = false, + var mButtonR: Boolean = false, + var mTriggerZL: Float = 0.0f, + var mTriggerZR: Float = 0.0f, + var mButtonUp: Boolean = false, + var mButtonDown: Boolean = false, + var mButtonLeft: Boolean = false, + var mButtonRight: Boolean = false, + var mButtonJoyLeft: Boolean = false, + var mButtonJoyRight: Boolean = false, + + var mJoyLeftX: Float = 0.0f, + var mJoyLeftY: Float = 0.0f, + var mJoyRightX: Float = 0.0f, + var mJoyRightY: Float = 0.0f, + + var mDeviceID: Int = 0 + ) class EGLConfigChooser : GLSurfaceView.EGLConfigChooser { // this comes from EGL14, but is unavailable on EGL10 From a241916cf565873347f55e7cd08c8a4e8bad87e9 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Wed, 4 Jun 2025 12:57:42 +0100 Subject: [PATCH 10/16] move to callback based system --- .../geode/launcher/GeometryDashActivity.kt | 33 +++++++++++++++---- .../com/geode/launcher/utils/GeodeUtils.kt | 25 ++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index f0396a13..ea6aaa10 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -9,6 +9,7 @@ import android.opengl.GLSurfaceView import android.os.Build import android.os.Bundle import android.os.Environment +import android.os.Handler import android.text.InputType import android.util.Log import android.view.InputDevice @@ -645,9 +646,11 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL override fun dispatchGenericMotionEvent(event: MotionEvent?): Boolean { event ?: return super.dispatchGenericMotionEvent(null) - fun processJoystick(event: MotionEvent, index: Int) { - val gamepad = mGamepads.firstOrNull { it.mDeviceID == event.deviceId } ?: return + val index = mGamepads.indexOfFirst { it.mDeviceID == event.deviceId } + if (index == -1) return super.dispatchGenericMotionEvent(null) + val gamepad = mGamepads[index] + fun processJoystick(event: MotionEvent, index: Int) { if (index < 0) { gamepad.mJoyLeftX = event.getAxisValue(MotionEvent.AXIS_X) gamepad.mJoyLeftY = -event.getAxisValue(MotionEvent.AXIS_Y) @@ -685,13 +688,20 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL // Process the current movement sample in the batch (position -1) processJoystick(event, -1) + // call callback in main thread + Handler(mainLooper).post { + if (GeodeUtils.controllerCallbacksEnabled()) GeodeUtils.setControllerState(index, gamepad) + } + return true; } override fun dispatchKeyEvent(event: KeyEvent): Boolean { - val gamepad = mGamepads.firstOrNull { it.mDeviceID == event.deviceId } ?: return super.dispatchKeyEvent(event) + val index = mGamepads.indexOfFirst { it.mDeviceID == event.deviceId } + if (index == -1) return super.dispatchKeyEvent(event) + val gamepad = mGamepads[index] - return when (event.keyCode) { + val changed = when (event.keyCode) { KeyEvent.KEYCODE_BUTTON_A -> { gamepad.mButtonA = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_B -> { gamepad.mButtonB = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_X -> { gamepad.mButtonX = event.action == KeyEvent.ACTION_DOWN; true } @@ -709,11 +719,22 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL KeyEvent.KEYCODE_DPAD_RIGHT -> { gamepad.mButtonRight = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_THUMBL -> { gamepad.mButtonJoyLeft = event.action == KeyEvent.ACTION_DOWN; true } KeyEvent.KEYCODE_BUTTON_THUMBR -> { gamepad.mButtonJoyRight = event.action == KeyEvent.ACTION_DOWN; true } - else -> super.dispatchKeyEvent(event) + else -> false } + + // call callback in main thread + if (changed) { + Handler(mainLooper).post { + if (GeodeUtils.controllerCallbacksEnabled()) GeodeUtils.setControllerState(index, gamepad) + } + + return true + } + + return super.dispatchKeyEvent(event) } - fun getGamepad(id: Int): Gamepad? { return mGamepads.getOrNull(id) } + fun getDeviceIDForGamepad(id: Int) = mGamepads.getOrNull(id)?.mDeviceID data class Gamepad( var mButtonA: Boolean = false, diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index 88127892..7c240839 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -56,6 +56,8 @@ object GeodeUtils { private var afterRequestPermissions: (() -> Unit)? = null private var afterRequestPermissionsFailure: (() -> Unit)? = null + private var mControllerCallbacksEnabled: Boolean = false + fun setContext(activity: AppCompatActivity) { this.activity = WeakReference(activity) openFileResultLauncher = activity.registerForActivityResult(GeodeOpenFileActivityResult()) { uri -> @@ -576,12 +578,9 @@ object GeodeUtils { } @JvmStatic - fun getControllerState(id: Int): GeometryDashActivity.Gamepad? { - val act = activity.get() - return if (act is GeometryDashActivity) { - act.getGamepad(id) - } else null - } + fun enableControllerCallbacks() = run { mControllerCallbacksEnabled = true } + @JvmStatic + fun controllerCallbacksEnabled() = mControllerCallbacksEnabled // whether this controller supports vibration or lighting @JvmStatic @@ -593,8 +592,8 @@ object GeodeUtils { val act = activity.get() if (act is GeometryDashActivity) { - val gamepad = act.getGamepad(id) ?: return - val device = InputDevice.getDevice(gamepad.mDeviceID) ?: return + val gamepad = act.getDeviceIDForGamepad(id) ?: return + val device = InputDevice.getDevice(gamepad) ?: return val manager = device.vibratorManager val ids = manager.vibratorIds @@ -615,8 +614,8 @@ object GeodeUtils { val act = activity.get() if (act is GeometryDashActivity) { - val gamepad = act.getGamepad(id) ?: return - val device = InputDevice.getDevice(gamepad.mDeviceID) ?: return + val gamepad = act.getDeviceIDForGamepad(id) ?: return + val device = InputDevice.getDevice(gamepad) ?: return val manager = device.lightsManager val request = LightsRequest.Builder() @@ -643,4 +642,10 @@ object GeodeUtils { * @see reportPlatformCapability */ external fun setNextInputTimestamp(timestamp: Long) + + /** + * Gives the state of the current controller at the index, whenever it updates. + * @see enableControllerCallbacks + */ + external fun setControllerState(index: Int, gamepad: GeometryDashActivity.Gamepad) } \ No newline at end of file From cf774119afde012d407453bb6eb466449b6ddfe4 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Wed, 4 Jun 2025 15:44:03 +0100 Subject: [PATCH 11/16] device connected callbacks --- .../geode/launcher/GeometryDashActivity.kt | 54 ++++++++++++++----- .../com/geode/launcher/utils/GeodeUtils.kt | 10 +++- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index ea6aaa10..fb5bb5ea 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo +import android.hardware.input.InputManager import android.opengl.GLSurfaceView import android.os.Build import android.os.Bundle @@ -68,7 +69,7 @@ fun ratioForPreference(value: String) = when (value) { else -> 1.77f } -class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperListener, GeodeUtils.CapabilityListener { +class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperListener, GeodeUtils.CapabilityListener, InputManager.InputDeviceListener { private var mGLSurfaceView: Cocos2dxGLSurfaceView? = null private var mIsRunning = false private var mIsOnPause = false @@ -82,6 +83,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL private var mScreenZoomFit = false private var mGamepads = mutableListOf() + private lateinit var mInputManager: InputManager override fun onCreate(savedInstanceState: Bundle?) { setupUIState() @@ -136,18 +138,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL mGLSurfaceView?.manualBackEvents = true } - // basically taken from documentation - val deviceIDs = InputDevice.getDeviceIds() - for (id in deviceIDs) { - val device = InputDevice.getDevice(id) ?: continue - - if (device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD - || device.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) { - mGamepads.add(Gamepad().apply { - mDeviceID = device.id - }) - } - } + mInputManager = getSystemService(INPUT_SERVICE) as InputManager + mInputManager.registerInputDeviceListener(this, null) + updateControllerDeviceIDs() } private fun createVersionFile() { @@ -643,6 +636,41 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL } } + private fun updateControllerDeviceIDs() { + mGamepads.clear() + + // basically taken from documentation + val deviceIDs = InputDevice.getDeviceIds() + for (id in deviceIDs) { + val device = InputDevice.getDevice(id) ?: continue + + if (device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + || device.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) { + mGamepads.add(Gamepad().apply { + mDeviceID = device.id + }) + } + } + } + + override fun onInputDeviceAdded(deviceID: Int) { + updateControllerDeviceIDs() + if (GeodeUtils.controllerCallbacksEnabled()) { + val index = mGamepads.indexOfFirst { it.mDeviceID == deviceID } + GeodeUtils.setControllerConnected(index, true) + } + } + + override fun onInputDeviceRemoved(deviceID: Int) { + if (GeodeUtils.controllerCallbacksEnabled()) { + val index = mGamepads.indexOfFirst { it.mDeviceID == deviceID } + GeodeUtils.setControllerConnected(index, false) + } + updateControllerDeviceIDs() + } + + override fun onInputDeviceChanged(deviceID: Int) {} + override fun dispatchGenericMotionEvent(event: MotionEvent?): Boolean { event ?: return super.dispatchGenericMotionEvent(null) diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index 7c240839..d134d0a3 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -577,12 +577,19 @@ object GeodeUtils { .launchUrl(activity, url.toUri()) } + /** + * Enables calling of the controller callbacks - they **must** be defined in native functions before this is called + * @see setControllerState + * @see setControllerConnected + */ @JvmStatic fun enableControllerCallbacks() = run { mControllerCallbacksEnabled = true } @JvmStatic fun controllerCallbacksEnabled() = mControllerCallbacksEnabled - // whether this controller supports vibration or lighting + /** + * Whether the **device** supports vibration or lighting effects - not necessarily if the controller can or not. + */ @JvmStatic fun supportsControllerExtendedFeatures(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S @@ -648,4 +655,5 @@ object GeodeUtils { * @see enableControllerCallbacks */ external fun setControllerState(index: Int, gamepad: GeometryDashActivity.Gamepad) + external fun setControllerConnected(index: Int, connected: Boolean) } \ No newline at end of file From 1398afe05e44950a811798e89219596f02be689f Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Wed, 4 Jun 2025 15:46:56 +0100 Subject: [PATCH 12/16] call callback in main thread --- app/src/main/java/com/geode/launcher/GeometryDashActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index fb5bb5ea..7ca86b8d 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -657,14 +657,14 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL updateControllerDeviceIDs() if (GeodeUtils.controllerCallbacksEnabled()) { val index = mGamepads.indexOfFirst { it.mDeviceID == deviceID } - GeodeUtils.setControllerConnected(index, true) + Handler(mainLooper).post { GeodeUtils.setControllerConnected(index, true) } } } override fun onInputDeviceRemoved(deviceID: Int) { if (GeodeUtils.controllerCallbacksEnabled()) { val index = mGamepads.indexOfFirst { it.mDeviceID == deviceID } - GeodeUtils.setControllerConnected(index, false) + Handler(mainLooper).post { GeodeUtils.setControllerConnected(index, false) } } updateControllerDeviceIDs() } From 2a86277dd5bc653411fa0c82b221b541ced2ffa0 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Wed, 4 Jun 2025 15:51:00 +0100 Subject: [PATCH 13/16] forgot to unregister input device listener --- app/src/main/java/com/geode/launcher/GeometryDashActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 7ca86b8d..81f3bb3f 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -523,6 +523,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL override fun onDestroy() { super.onDestroy() FMOD.close() + mInputManager.unregisterInputDeviceListener(this) } private fun resumeGame() { From 9e00f16c98be2172cc3e161eddf1726621e20153 Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Wed, 4 Jun 2025 20:07:23 +0100 Subject: [PATCH 14/16] add get gamepad count --- .../main/java/com/geode/launcher/GeometryDashActivity.kt | 1 + app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 81f3bb3f..8f18edc9 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -764,6 +764,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL } fun getDeviceIDForGamepad(id: Int) = mGamepads.getOrNull(id)?.mDeviceID + fun getGamepadCount() = mGamepads.size data class Gamepad( var mButtonA: Boolean = false, diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index d134d0a3..cce62b60 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -577,13 +577,19 @@ object GeodeUtils { .launchUrl(activity, url.toUri()) } + @JvmStatic + fun getControllerCount(): Int { + val act = activity.get() + return if (act is GeometryDashActivity) act.getGamepadCount() else 0 + } + /** * Enables calling of the controller callbacks - they **must** be defined in native functions before this is called * @see setControllerState * @see setControllerConnected */ @JvmStatic - fun enableControllerCallbacks() = run { mControllerCallbacksEnabled = true } + fun enableControllerCallbacks() { mControllerCallbacksEnabled = true } @JvmStatic fun controllerCallbacksEnabled() = mControllerCallbacksEnabled From 81edd8c3c0cfcbe1f67cd40fc389ec3d10faa33d Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Wed, 4 Jun 2025 20:07:33 +0100 Subject: [PATCH 15/16] remove extra space --- app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index cce62b60..598e45e8 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -589,7 +589,7 @@ object GeodeUtils { * @see setControllerConnected */ @JvmStatic - fun enableControllerCallbacks() { mControllerCallbacksEnabled = true } + fun enableControllerCallbacks() { mControllerCallbacksEnabled = true } @JvmStatic fun controllerCallbacksEnabled() = mControllerCallbacksEnabled From fd91c387a48fa5831c27287802c9aa91be86c4ef Mon Sep 17 00:00:00 2001 From: undefined06855 Date: Tue, 10 Jun 2025 16:32:38 +0100 Subject: [PATCH 16/16] move gamepad class into geodeutils --- .../geode/launcher/GeometryDashActivity.kt | 34 +---------- .../com/geode/launcher/utils/GeodeUtils.kt | 57 +++++++++++++++---- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt index 8f18edc9..1e0c28d2 100644 --- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt +++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt @@ -82,7 +82,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL private var mScreenZoom = 1.0f private var mScreenZoomFit = false - private var mGamepads = mutableListOf() + private var mGamepads = mutableListOf() private lateinit var mInputManager: InputManager override fun onCreate(savedInstanceState: Bundle?) { @@ -647,9 +647,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL if (device.sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || device.sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) { - mGamepads.add(Gamepad().apply { - mDeviceID = device.id - }) + mGamepads.add(GeodeUtils.Gamepad(device.id)) } } } @@ -763,35 +761,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL return super.dispatchKeyEvent(event) } - fun getDeviceIDForGamepad(id: Int) = mGamepads.getOrNull(id)?.mDeviceID + fun getGamepad(id: Int) = mGamepads.getOrNull(id) fun getGamepadCount() = mGamepads.size - data class Gamepad( - var mButtonA: Boolean = false, - var mButtonB: Boolean = false, - var mButtonX: Boolean = false, - var mButtonY: Boolean = false, - var mButtonStart: Boolean = false, - var mButtonSelect: Boolean = false, - var mButtonL: Boolean = false, - var mButtonR: Boolean = false, - var mTriggerZL: Float = 0.0f, - var mTriggerZR: Float = 0.0f, - var mButtonUp: Boolean = false, - var mButtonDown: Boolean = false, - var mButtonLeft: Boolean = false, - var mButtonRight: Boolean = false, - var mButtonJoyLeft: Boolean = false, - var mButtonJoyRight: Boolean = false, - - var mJoyLeftX: Float = 0.0f, - var mJoyLeftY: Float = 0.0f, - var mJoyRightX: Float = 0.0f, - var mJoyRightY: Float = 0.0f, - - var mDeviceID: Int = 0 - ) - class EGLConfigChooser : GLSurfaceView.EGLConfigChooser { // this comes from EGL14, but is unavailable on EGL10 // also EGL14 is incompatible with EGL10. so whatever diff --git a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt index 598e45e8..9e55fc4b 100644 --- a/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt +++ b/app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt @@ -601,12 +601,49 @@ object GeodeUtils { @JvmStatic fun setControllerVibration(id: Int, duration: Long, left: Int, right: Int) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + val act = activity.get() + if (act is GeometryDashActivity) { + act.getGamepad(id)?.setVibration(duration, left, right) + } + } + @JvmStatic + fun setControllerColor(id: Int, color: Int) { val act = activity.get() if (act is GeometryDashActivity) { - val gamepad = act.getDeviceIDForGamepad(id) ?: return - val device = InputDevice.getDevice(gamepad) ?: return + act.getGamepad(id)?.setColor(color) + } + } + + class Gamepad(deviceID: Int) { + var mButtonA: Boolean = false + var mButtonB: Boolean = false + var mButtonX: Boolean = false + var mButtonY: Boolean = false + var mButtonStart: Boolean = false + var mButtonSelect: Boolean = false + var mButtonL: Boolean = false + var mButtonR: Boolean = false + var mTriggerZL: Float = 0.0f + var mTriggerZR: Float = 0.0f + var mButtonUp: Boolean = false + var mButtonDown: Boolean = false + var mButtonLeft: Boolean = false + var mButtonRight: Boolean = false + var mButtonJoyLeft: Boolean = false + var mButtonJoyRight: Boolean = false + + var mJoyLeftX: Float = 0.0f + var mJoyLeftY: Float = 0.0f + var mJoyRightX: Float = 0.0f + var mJoyRightY: Float = 0.0f + + var mDeviceID: Int = deviceID + + fun setVibration(duration: Long, left: Int, right: Int) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + + val device = InputDevice.getDevice(mDeviceID) ?: return val manager = device.vibratorManager val ids = manager.vibratorIds @@ -619,16 +656,11 @@ object GeodeUtils { manager.getVibrator(ids[1]).vibrate(VibrationEffect.createOneShot(duration, right)) } } - } - @JvmStatic - fun setControllerColor(id: Int, color: Int) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return + fun setColor(color: Int) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return - val act = activity.get() - if (act is GeometryDashActivity) { - val gamepad = act.getDeviceIDForGamepad(id) ?: return - val device = InputDevice.getDevice(gamepad) ?: return + val device = InputDevice.getDevice(mDeviceID) ?: return val manager = device.lightsManager val request = LightsRequest.Builder() @@ -640,6 +672,7 @@ object GeodeUtils { .build() ) } + manager.openSession().requestLights(request.build()) } } @@ -660,6 +693,6 @@ object GeodeUtils { * Gives the state of the current controller at the index, whenever it updates. * @see enableControllerCallbacks */ - external fun setControllerState(index: Int, gamepad: GeometryDashActivity.Gamepad) + external fun setControllerState(index: Int, gamepad: Gamepad) external fun setControllerConnected(index: Int, connected: Boolean) } \ No newline at end of file