@@ -7,6 +7,9 @@ import android.content.ClipboardManager
7
7
import android.content.Context
8
8
import android.content.Intent
9
9
import android.content.pm.PackageManager
10
+ import android.hardware.lights.Light
11
+ import android.hardware.lights.LightState
12
+ import android.hardware.lights.LightsRequest
10
13
import android.net.Uri
11
14
import android.os.Build
12
15
import android.os.Environment
@@ -16,6 +19,7 @@ import android.os.VibratorManager
16
19
import android.provider.DocumentsContract
17
20
import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
18
21
import android.util.Log
22
+ import android.view.InputDevice
19
23
import android.widget.Toast
20
24
import androidx.activity.result.ActivityResultLauncher
21
25
import androidx.activity.result.contract.ActivityResultContracts
@@ -547,6 +551,7 @@ object GeodeUtils {
547
551
const val CAPABILITY_EXTENDED_INPUT = " extended_input"
548
552
const val CAPABILITY_TIMESTAMP_INPUT = " timestamp_inputs"
549
553
const val CAPABILITY_INTERNAL_CALLBACKS = " internal_callbacks_v1"
554
+ const val CAPABILITY_CONTROLLER_DATA = " controller_data"
550
555
551
556
private var capabilityListener: WeakReference <CapabilityListener ?> = WeakReference (null )
552
557
@@ -576,6 +581,188 @@ object GeodeUtils {
576
581
.launchUrl(activity, url.toUri())
577
582
}
578
583
584
+ /* *
585
+ * Determines if any gamepad or joystick input devices are connected.
586
+ * This is useful during start, but tracking inputDeviceAdded events is better during runtime.
587
+ */
588
+ @JvmStatic
589
+ fun controllersConnected (): Int {
590
+ val deviceIds = InputDevice .getDeviceIds()
591
+ return deviceIds.count {
592
+ InputDevice .getDevice(it)?.run {
593
+ sources and InputDevice .SOURCE_GAMEPAD == InputDevice .SOURCE_GAMEPAD || sources and InputDevice .SOURCE_JOYSTICK == InputDevice .SOURCE_JOYSTICK
594
+ } ? : false
595
+ }
596
+ }
597
+
598
+ @JvmStatic
599
+ fun getConnectedDevices (): IntArray = InputDevice .getDeviceIds()
600
+
601
+ @JvmStatic
602
+ fun getDevice (deviceId : Int ): InputDevice ? = InputDevice .getDevice(deviceId)
603
+
604
+ @JvmStatic
605
+ fun getDeviceBatteryCapacity (deviceId : Int ): Float {
606
+ val device = InputDevice .getDevice(deviceId) ? : return 0.0f
607
+ return if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
608
+ device.batteryState.capacity
609
+ } else {
610
+ 0.0f
611
+ }
612
+ }
613
+
614
+ @JvmStatic
615
+ fun deviceHasBattery (deviceId : Int ): Boolean {
616
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S ) {
617
+ return false
618
+ }
619
+
620
+ val device = InputDevice .getDevice(deviceId) ? : return false
621
+ return device.batteryState.isPresent
622
+ }
623
+
624
+ @JvmStatic
625
+ fun getDeviceBatteryStatus (deviceId : Int ): Int {
626
+ val device = InputDevice .getDevice(deviceId) ? : return 0
627
+ return if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
628
+ device.batteryState.status
629
+ } else {
630
+ 0
631
+ }
632
+ }
633
+
634
+ @JvmStatic
635
+ fun getDeviceLightsCount (deviceId : Int ): Int {
636
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S ) {
637
+ return 0
638
+ }
639
+
640
+ val device = InputDevice .getDevice(deviceId) ? : return 0
641
+ val lightsManager = device.lightsManager
642
+
643
+ return lightsManager.lights.size
644
+ }
645
+
646
+ // this is based on paddleboat's api
647
+ @JvmStatic
648
+ fun getLightType (deviceId : Int ): Int {
649
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S ) {
650
+ return 0
651
+ }
652
+
653
+ val device = InputDevice .getDevice(deviceId) ? : return 0
654
+ val lightsManager = device.lightsManager
655
+
656
+ var flags = 0
657
+ lightsManager.lights.forEach { light ->
658
+ if (light.type == Light .LIGHT_TYPE_PLAYER_ID ) {
659
+ flags = flags or 1
660
+ } else if (light.hasRgbControl()) {
661
+ flags = flags or 2
662
+ }
663
+ }
664
+
665
+ return flags
666
+ }
667
+
668
+ /* *
669
+ * Sets the color for a device.
670
+ * @return false if the given device does not exist or setting lights was otherwise unsuccessful
671
+ */
672
+ @JvmStatic
673
+ fun setDeviceLightColor (deviceId : Int , color : Int , type : Int ): Boolean {
674
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S ) {
675
+ return false
676
+ }
677
+
678
+ val device = InputDevice .getDevice(deviceId) ? : return false
679
+ val lightsManager = device.lightsManager
680
+
681
+ val lights = lightsManager.lights
682
+ if (lights.isEmpty()) {
683
+ return false
684
+ }
685
+
686
+ val actedLights = lights.count { light ->
687
+ val state = LightState .Builder ()
688
+
689
+ if (light.type == Light .LIGHT_TYPE_PLAYER_ID && (type and 1 == 1 )) {
690
+ state.setPlayerId(color)
691
+ } else if (light.hasRgbControl() && (type and 2 == 2 )) {
692
+ state.setColor(color)
693
+ } else {
694
+ return @count false
695
+ }
696
+
697
+ val request = LightsRequest .Builder ()
698
+ .addLight(light, state.build())
699
+ .build()
700
+
701
+ lightsManager.openSession()
702
+ .requestLights(request)
703
+
704
+ true
705
+ }
706
+
707
+ return actedLights > 0
708
+ }
709
+
710
+ @JvmStatic
711
+ fun getDeviceHapticsCount (deviceId : Int ): Int {
712
+ val device = InputDevice .getDevice(deviceId) ? : return 0
713
+
714
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S ) {
715
+ return if (device.vibrator.hasVibrator()) 1 else 0
716
+ }
717
+
718
+ val vibratorManager = device.vibratorManager
719
+ val vibratorIds = vibratorManager.vibratorIds
720
+ return vibratorIds.size
721
+ }
722
+
723
+ private fun performVibration (vibrator : Vibrator ? , durationMs : Long , intensity : Int ) {
724
+ vibrator?.apply {
725
+ if (intensity == 0 ) {
726
+ cancel()
727
+ } else {
728
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
729
+ val vibrationEffect = VibrationEffect .createOneShot(durationMs, intensity)
730
+ vibrate(vibrationEffect)
731
+ } else {
732
+ vibrate(durationMs)
733
+ }
734
+ }
735
+ }
736
+ }
737
+
738
+ /* *
739
+ * Vibrates a given device. Set motorIdx == -1 to vibrate all available motors on a device.
740
+ */
741
+ @JvmStatic
742
+ fun vibrateDevice (deviceId : Int , durationMs : Long , intensity : Int , motorIdx : Int ): Boolean {
743
+ val device = InputDevice .getDevice(deviceId) ? : return false
744
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
745
+ val vibratorManager = device.vibratorManager
746
+ val vibratorIds = vibratorManager.vibratorIds
747
+
748
+ if (motorIdx == - 1 ) {
749
+ vibratorIds.forEach {
750
+ val vibrator = vibratorManager.getVibrator(it)
751
+ performVibration(vibrator, durationMs, intensity)
752
+ }
753
+ } else {
754
+ val deviceId = vibratorIds.getOrNull(motorIdx) ? : return false
755
+ val vibrator = vibratorManager.getVibrator(deviceId)
756
+ performVibration(vibrator, durationMs, intensity)
757
+ }
758
+ } else {
759
+ val vibrator = device.vibrator
760
+ performVibration(vibrator, durationMs, intensity)
761
+ }
762
+
763
+ return true
764
+ }
765
+
579
766
external fun nativeKeyUp (keyCode : Int , modifiers : Int )
580
767
external fun nativeKeyDown (keyCode : Int , modifiers : Int , isRepeating : Boolean )
581
768
external fun nativeActionScroll (scrollX : Float , scrollY : Float )
@@ -588,4 +775,15 @@ object GeodeUtils {
588
775
*/
589
776
external fun setNextInputTimestamp (timestamp : Long )
590
777
external fun setNextInputTimestampInternal (timestamp : Long )
778
+
779
+ external fun setNextInputDevice (deviceId : Int , eventSource : Int )
780
+ external fun inputDeviceAdded (deviceId : Int )
781
+ external fun inputDeviceChanged (deviceId : Int )
782
+ external fun inputDeviceRemoved (deviceId : Int )
783
+
784
+ /* *
785
+ * Sends all batched joystick events. The final position in each array represents the current position of that axis.
786
+ * Have fun!
787
+ */
788
+ external fun onJoystickEvent (leftX : FloatArray , leftY : FloatArray , rightX : FloatArray , rightY : FloatArray , hatX : FloatArray , hatY : FloatArray , leftTrigger : FloatArray , rightTrigger : FloatArray )
591
789
}
0 commit comments