my attempt 2

This commit is contained in:
Chloe committed 2025-09-20 17:41:40 -07:00
1 parent 2797e52683
commit cf4cd8335e
4 files changed
+355

No files matched your search

+4
View File
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.LIGHTS" />
<uses-feature <uses-feature
android:name="android.hardware.wifi" android:name="android.hardware.wifi"
@@ -29,6 +30,9 @@
<uses-feature <uses-feature
android:glEsVersion="0x00020000" android:glEsVersion="0x00020000"
android:required="true" /> android:required="true" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -592,6 +592,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
mGLSurfaceView?.sendInternalTimestampEvents = true mGLSurfaceView?.sendInternalTimestampEvents = true
true true
} }
GeodeUtils.CAPABILITY_CONTROLLER_DATA -> {
mGLSurfaceView?.enableControllerEvents()
true
}
else -> false else -> false
} }
} }
@@ -7,6 +7,9 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.hardware.lights.Light
import android.hardware.lights.LightState
import android.hardware.lights.LightsRequest
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
@@ -16,6 +19,7 @@ import android.os.VibratorManager
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
import android.util.Log import android.util.Log
import android.view.InputDevice
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -547,6 +551,7 @@ object GeodeUtils {
const val CAPABILITY_EXTENDED_INPUT = "extended_input" const val CAPABILITY_EXTENDED_INPUT = "extended_input"
const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs" const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs"
const val CAPABILITY_INTERNAL_CALLBACKS = "internal_callbacks_v1" const val CAPABILITY_INTERNAL_CALLBACKS = "internal_callbacks_v1"
const val CAPABILITY_CONTROLLER_DATA = "controller_data"
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null) private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
@@ -576,6 +581,188 @@ object GeodeUtils {
.launchUrl(activity, url.toUri()) .launchUrl(activity, url.toUri())
} }
/**
* Determines if any gamepad or joystick input devices are connected.
* This is useful during start, but tracking inputDeviceAdded events is better during runtime.
*/
@JvmStatic
fun controllersConnected(): Int {
val deviceIds = InputDevice.getDeviceIds()
return deviceIds.count {
InputDevice.getDevice(it)?.run {
sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
} ?: false
}
}
@JvmStatic
fun getConnectedDevices(): IntArray = InputDevice.getDeviceIds()
@JvmStatic
fun getDevice(deviceId: Int): InputDevice? = InputDevice.getDevice(deviceId)
@JvmStatic
fun getDeviceBatteryCapacity(deviceId: Int): Float {
val device = InputDevice.getDevice(deviceId) ?: return 0.0f
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
device.batteryState.capacity
} else {
0.0f
}
}
@JvmStatic
fun deviceHasBattery(deviceId: Int): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return false
}
val device = InputDevice.getDevice(deviceId) ?: return false
return device.batteryState.isPresent
}
@JvmStatic
fun getDeviceBatteryStatus(deviceId: Int): Int {
val device = InputDevice.getDevice(deviceId) ?: return 0
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
device.batteryState.status
} else {
0
}
}
@JvmStatic
fun getDeviceLightsCount(deviceId: Int): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return 0
}
val device = InputDevice.getDevice(deviceId) ?: return 0
val lightsManager = device.lightsManager
return lightsManager.lights.size
}
// this is based on paddleboat's api
@JvmStatic
fun getLightType(deviceId: Int): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return 0
}
val device = InputDevice.getDevice(deviceId) ?: return 0
val lightsManager = device.lightsManager
var flags = 0
lightsManager.lights.forEach { light ->
if (light.type == Light.LIGHT_TYPE_PLAYER_ID) {
flags = flags or 1
} else if (light.hasRgbControl()) {
flags = flags or 2
}
}
return flags
}
/**
* Sets the color for a device.
* @return false if the given device does not exist or setting lights was otherwise unsuccessful
*/
@JvmStatic
fun setDeviceLightColor(deviceId: Int, color: Int, type: Int): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return false
}
val device = InputDevice.getDevice(deviceId) ?: return false
val lightsManager = device.lightsManager
val lights = lightsManager.lights
if (lights.isEmpty()) {
return false
}
val actedLights = lights.count { light ->
val state = LightState.Builder()
if (light.type == Light.LIGHT_TYPE_PLAYER_ID && (type and 1 == 1)) {
state.setPlayerId(color)
} else if (light.hasRgbControl() && (type and 2 == 2)) {
state.setColor(color)
} else {
return@count false
}
val request = LightsRequest.Builder()
.addLight(light, state.build())
.build()
lightsManager.openSession()
.requestLights(request)
true
}
return actedLights > 0
}
@JvmStatic
fun getDeviceHapticsCount(deviceId: Int): Int {
val device = InputDevice.getDevice(deviceId) ?: return 0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return if (device.vibrator.hasVibrator()) 1 else 0
}
val vibratorManager = device.vibratorManager
val vibratorIds = vibratorManager.vibratorIds
return vibratorIds.size
}
private fun performVibration(vibrator: Vibrator?, durationMs: Long, intensity: Int) {
vibrator?.apply {
if (intensity == 0) {
cancel()
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect = VibrationEffect.createOneShot(durationMs, intensity)
vibrate(vibrationEffect)
} else {
vibrate(durationMs)
}
}
}
}
/**
* Vibrates a given device. Set motorIdx == -1 to vibrate all available motors on a device.
*/
@JvmStatic
fun vibrateDevice(deviceId: Int, durationMs: Long, intensity: Int, motorIdx: Int): Boolean {
val device = InputDevice.getDevice(deviceId) ?: return false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = device.vibratorManager
val vibratorIds = vibratorManager.vibratorIds
if (motorIdx == -1) {
vibratorIds.forEach {
val vibrator = vibratorManager.getVibrator(it)
performVibration(vibrator, durationMs, intensity)
}
} else {
val deviceId = vibratorIds.getOrNull(motorIdx) ?: return false
val vibrator = vibratorManager.getVibrator(deviceId)
performVibration(vibrator, durationMs, intensity)
}
} else {
val vibrator = device.vibrator
performVibration(vibrator, durationMs, intensity)
}
return true
}
external fun nativeKeyUp(keyCode: Int, modifiers: Int) external fun nativeKeyUp(keyCode: Int, modifiers: Int)
external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean) external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean)
external fun nativeActionScroll(scrollX: Float, scrollY: Float) external fun nativeActionScroll(scrollX: Float, scrollY: Float)
@@ -588,4 +775,15 @@ object GeodeUtils {
*/ */
external fun setNextInputTimestamp(timestamp: Long) external fun setNextInputTimestamp(timestamp: Long)
external fun setNextInputTimestampInternal(timestamp: Long) external fun setNextInputTimestampInternal(timestamp: Long)
external fun setNextInputDevice(deviceId: Int, eventSource: Int)
external fun inputDeviceAdded(deviceId: Int)
external fun inputDeviceChanged(deviceId: Int)
external fun inputDeviceRemoved(deviceId: Int)
/**
* Sends all batched joystick events. The final position in each array represents the current position of that axis.
* Have fun!
*/
external fun onJoystickEvent(leftX: FloatArray, leftY: FloatArray, rightX: FloatArray, rightY: FloatArray, hatX: FloatArray, hatY: FloatArray, leftTrigger: FloatArray, rightTrigger: FloatArray)
} }
@@ -2,12 +2,14 @@ package org.cocos2dx.lib
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.hardware.input.InputManager
import android.opengl.GLSurfaceView import android.opengl.GLSurfaceView
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.util.Log import android.util.Log
import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.Surface import android.view.Surface
@@ -16,6 +18,7 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatEditText
import com.customRobTop.BaseRobTopActivity import com.customRobTop.BaseRobTopActivity
import com.geode.launcher.utils.GeodeUtils import com.geode.launcher.utils.GeodeUtils
import kotlin.math.abs
private const val HANDLER_OPEN_IME_KEYBOARD = 2 private const val HANDLER_OPEN_IME_KEYBOARD = 2
private const val HANDLER_CLOSE_IME_KEYBOARD = 3 private const val HANDLER_CLOSE_IME_KEYBOARD = 3
@@ -58,6 +61,8 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
var sendTimestampEvents = false var sendTimestampEvents = false
var sendInternalTimestampEvents = false var sendInternalTimestampEvents = false
var manualBackEvents = false var manualBackEvents = false
var controllerEventsEnabled = false
private set
var cocos2dxEditText: AppCompatEditText? = null var cocos2dxEditText: AppCompatEditText? = null
set(value) { set(value) {
@@ -159,6 +164,16 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
GeodeUtils.setNextInputTimestampInternal(timestamp) GeodeUtils.setNextInputTimestampInternal(timestamp)
} }
private fun sendNextEventDetails(event: KeyEvent) {
if (controllerEventsEnabled)
GeodeUtils.setNextInputDevice(event.deviceId, event.source)
}
private fun sendNextEventDetails(event: MotionEvent) {
if (controllerEventsEnabled)
GeodeUtils.setNextInputDevice(event.deviceId, event.source)
}
override fun onTouchEvent(motionEvent: MotionEvent): Boolean { override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
val pointerNumber = motionEvent.pointerCount val pointerNumber = motionEvent.pointerCount
val ids = IntArray(pointerNumber) val ids = IntArray(pointerNumber)
@@ -178,6 +193,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val xDown = xs[0] val xDown = xs[0]
val f = ys[0] val f = ys[0]
queueEvent { queueEvent {
sendNextEventDetails(motionEvent)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
cocos2dxRenderer.handleActionDown(idDown, xDown, f) cocos2dxRenderer.handleActionDown(idDown, xDown, f)
} }
@@ -188,6 +204,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val f2 = xs[0] val f2 = xs[0]
val f3 = ys[0] val f3 = ys[0]
queueEvent { queueEvent {
sendNextEventDetails(motionEvent)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
cocos2dxRenderer.handleActionUp(idUp, f2, f3) cocos2dxRenderer.handleActionUp(idUp, f2, f3)
} }
@@ -195,6 +212,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
queueEvent { queueEvent {
sendNextEventDetails(motionEvent)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
cocos2dxRenderer.handleActionMove(ids, xs, ys) cocos2dxRenderer.handleActionMove(ids, xs, ys)
} }
@@ -202,6 +220,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_CANCEL -> {
queueEvent { queueEvent {
sendNextEventDetails(motionEvent)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
cocos2dxRenderer.handleActionCancel(ids, xs, ys) cocos2dxRenderer.handleActionCancel(ids, xs, ys)
} }
@@ -213,6 +232,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val xPointerDown = motionEvent.getX(indexPointerDown) val xPointerDown = motionEvent.getX(indexPointerDown)
val y = motionEvent.getY(indexPointerDown) val y = motionEvent.getY(indexPointerDown)
queueEvent { queueEvent {
sendNextEventDetails(motionEvent)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
cocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, y) cocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, y)
} }
@@ -224,6 +244,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val xPointerUp = motionEvent.getX(indexPointUp) val xPointerUp = motionEvent.getX(indexPointUp)
val y2 = motionEvent.getY(indexPointUp) val y2 = motionEvent.getY(indexPointUp)
queueEvent { queueEvent {
sendNextEventDetails(motionEvent)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
cocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, y2) cocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, y2)
} }
@@ -273,6 +294,9 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} else { } else {
val currentTime = System.nanoTime() val currentTime = System.nanoTime()
queueEvent { queueEvent {
if (controllerEventsEnabled)
GeodeUtils.setNextInputDevice(-1, InputDevice.SOURCE_ANY)
sendNextEventTimestamp(currentTime) sendNextEventTimestamp(currentTime)
GeodeUtils.nativeKeyDown(KeyEvent.KEYCODE_BACK, 0, false) GeodeUtils.nativeKeyDown(KeyEvent.KEYCODE_BACK, 0, false)
} }
@@ -295,6 +319,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
queueEvent { queueEvent {
sendNextEventDetails(event)
sendNextEventTimestamp(event.eventTime * MS_TO_NS) sendNextEventTimestamp(event.eventTime * MS_TO_NS)
GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0) GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0)
} }
@@ -318,6 +343,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
queueEvent { queueEvent {
sendNextEventDetails(event)
sendNextEventTimestamp(event.eventTime * MS_TO_NS) sendNextEventTimestamp(event.eventTime * MS_TO_NS)
GeodeUtils.nativeKeyUp(keyCode, event.modifiers) GeodeUtils.nativeKeyUp(keyCode, event.modifiers)
} }
@@ -327,6 +353,65 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
} }
// i'm lazy.
// https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
private fun getCenteredAxis(
event: MotionEvent,
device: InputDevice,
axis: Int,
historyPos: Int
): Float {
device.getMotionRange(axis, event.source)?.apply {
val value: Float = if (historyPos < 0) {
event.getAxisValue(axis)
} else {
event.getHistoricalAxisValue(axis, historyPos)
}
if (abs(value) > flat) {
return value
}
}
return 0.0f
}
private fun getJoystickMotion(event: MotionEvent, axisHorizontal: Int, axisVertical: Int): Pair<FloatArray, FloatArray> {
val positionChangesX = ArrayList<Float>()
val positionChangesY = ArrayList<Float>()
val inputDevice = event.device
(0 until event.historySize).forEach { i ->
val x = getCenteredAxis(event, inputDevice, axisHorizontal, i)
val y = getCenteredAxis(event, inputDevice, axisVertical, i)
positionChangesX.add(x)
positionChangesY.add(y)
}
val x = getCenteredAxis(event, inputDevice, axisHorizontal, -1)
val y = getCenteredAxis(event, inputDevice, axisVertical, -1)
positionChangesX.add(x)
positionChangesY.add(y)
return Pair(positionChangesX.toFloatArray(), positionChangesY.toFloatArray())
}
private fun getJoystickMotion(event: MotionEvent, axis: Int): FloatArray {
val positionChanges = ArrayList<Float>()
val inputDevice = event.device
(0 until event.historySize).forEach { i ->
val x = getCenteredAxis(event, inputDevice, axis, i)
positionChanges.add(x)
}
val x = getCenteredAxis(event, inputDevice, axis, -1)
positionChanges.add(x)
return positionChanges.toFloatArray()
}
override fun onGenericMotionEvent(event: MotionEvent?): Boolean { override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
if (!useKeyboardEvents) { if (!useKeyboardEvents) {
return super.onGenericMotionEvent(event) return super.onGenericMotionEvent(event)
@@ -339,6 +424,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
event.eventTimeNanos else event.eventTime * MS_TO_NS event.eventTimeNanos else event.eventTime * MS_TO_NS
queueEvent { queueEvent {
sendNextEventDetails(event)
sendNextEventTimestamp(timestamp) sendNextEventTimestamp(timestamp)
GeodeUtils.nativeActionScroll(scrollX, scrollY) GeodeUtils.nativeActionScroll(scrollX, scrollY)
} }
@@ -346,6 +432,33 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
return true return true
} }
if (controllerEventsEnabled && event?.action == MotionEvent.ACTION_MOVE && event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) {
val (motionLeftX, motionLeftY) = getJoystickMotion(event, MotionEvent.AXIS_X, MotionEvent.AXIS_Y)
val (motionRightX, motionRightY) = getJoystickMotion(event, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ)
val (motionDPadX, motionDPadY) = getJoystickMotion(event, MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y)
val motionLTrigger = getJoystickMotion(event, MotionEvent.AXIS_LTRIGGER)
val motionRTrigger = getJoystickMotion(event, MotionEvent.AXIS_RTRIGGER)
// i'm sure this is great for performance
val timestamp = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
event.eventTimeNanos else event.eventTime * MS_TO_NS
queueEvent {
sendNextEventDetails(event)
sendNextEventTimestamp(timestamp)
GeodeUtils.onJoystickEvent(
motionLeftX, motionLeftY,
motionRightX, motionRightY,
motionDPadX, motionDPadY,
motionLTrigger, motionRTrigger
)
}
return true
}
return super.onGenericMotionEvent(event) return super.onGenericMotionEvent(event)
} }
@@ -365,4 +478,40 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
fun deleteBackward() { fun deleteBackward() {
queueEvent { cocos2dxRenderer.handleDeleteBackward() } queueEvent { cocos2dxRenderer.handleDeleteBackward() }
} }
fun enableControllerEvents() {
if (controllerEventsEnabled) {
return
}
controllerEventsEnabled = true
val inputManager = context.getSystemService(Context.INPUT_SERVICE) as InputManager
inputManager.registerInputDeviceListener(InputListener, null)
}
fun disableControllerEvents() {
if (!controllerEventsEnabled) {
return
}
controllerEventsEnabled = true
val inputManager = context.getSystemService(Context.INPUT_SERVICE) as InputManager
inputManager.unregisterInputDeviceListener(InputListener)
}
object InputListener : InputManager.InputDeviceListener {
override fun onInputDeviceAdded(deviceId: Int) {
GeodeUtils.inputDeviceAdded(deviceId)
}
override fun onInputDeviceChanged(deviceId: Int) {
GeodeUtils.inputDeviceChanged(deviceId)
}
override fun onInputDeviceRemoved(deviceId: Int) {
GeodeUtils.inputDeviceRemoved(deviceId)
}
}
} }