mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-01 23:35:28 +00:00
Implement controller LED and battery state extensions
This commit is contained in:
parent
803ad116fb
commit
d4079940b4
@ -2257,6 +2257,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
controllerHandler.handleSetMotionEventState(controllerNumber, motionType, reportRateHz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setControllerLED(short controllerNumber, byte r, byte g, byte b) {
|
||||
controllerHandler.handleSetControllerLED(controllerNumber, r, g, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
if (!surfaceCreated) {
|
||||
|
@ -3,11 +3,16 @@ package com.limelight.binding.input;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.hardware.BatteryState;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.lights.Light;
|
||||
import android.hardware.lights.LightState;
|
||||
import android.hardware.lights.LightsManager;
|
||||
import android.hardware.lights.LightsRequest;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.AudioAttributes;
|
||||
@ -56,6 +61,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
private static final short MAX_GAMEPADS = 16; // Limited by bits in activeGamepadMask
|
||||
|
||||
private static final int BATTERY_RECHECK_INTERVAL_MS = 120 * 1000;
|
||||
|
||||
private static final Map<Integer, Integer> ANDROID_TO_LI_BUTTON_MAP = Map.ofEntries(
|
||||
Map.entry(KeyEvent.KEYCODE_BUTTON_A, ControllerPacket.A_FLAG),
|
||||
Map.entry(KeyEvent.KEYCODE_BUTTON_B, ControllerPacket.B_FLAG),
|
||||
@ -867,6 +874,68 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean areBatteryCapacitiesEqual(float first, float second) {
|
||||
// With no NaNs involved, it is a simple equality comparison.
|
||||
if (!Float.isNaN(first) && !Float.isNaN(second)) {
|
||||
return first == second;
|
||||
}
|
||||
else {
|
||||
// If we have a NaN in one or both positions, compare NaN-ness instead.
|
||||
// Equality comparisons will always return false for NaN.
|
||||
return Float.isNaN(first) == Float.isNaN(second);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendControllerBatteryPacket(InputDeviceContext context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
int currentBatteryStatus = context.inputDevice.getBatteryState().getStatus();
|
||||
float currentBatteryCapacity = context.inputDevice.getBatteryState().getCapacity();
|
||||
|
||||
if (currentBatteryStatus != context.lastReportedBatteryStatus ||
|
||||
!areBatteryCapacitiesEqual(currentBatteryCapacity, context.lastReportedBatteryCapacity)) {
|
||||
byte state;
|
||||
byte percentage;
|
||||
|
||||
switch (currentBatteryStatus) {
|
||||
case BatteryState.STATUS_UNKNOWN:
|
||||
state = MoonBridge.LI_BATTERY_STATE_UNKNOWN;
|
||||
break;
|
||||
|
||||
case BatteryState.STATUS_CHARGING:
|
||||
state = MoonBridge.LI_BATTERY_STATE_CHARGING;
|
||||
break;
|
||||
|
||||
case BatteryState.STATUS_DISCHARGING:
|
||||
state = MoonBridge.LI_BATTERY_STATE_DISCHARGING;
|
||||
break;
|
||||
|
||||
case BatteryState.STATUS_NOT_CHARGING:
|
||||
state = MoonBridge.LI_BATTERY_STATE_NOT_CHARGING;
|
||||
break;
|
||||
|
||||
case BatteryState.STATUS_FULL:
|
||||
state = MoonBridge.LI_BATTERY_STATE_FULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (Float.isNaN(currentBatteryCapacity)) {
|
||||
percentage = MoonBridge.LI_BATTERY_PERCENTAGE_UNKNOWN;
|
||||
}
|
||||
else {
|
||||
percentage = (byte)(currentBatteryCapacity * 100);
|
||||
}
|
||||
|
||||
conn.sendControllerBatteryEvent((byte)context.controllerNumber, state, percentage);
|
||||
|
||||
context.lastReportedBatteryStatus = currentBatteryStatus;
|
||||
context.lastReportedBatteryCapacity = currentBatteryCapacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendControllerInputPacket(GenericControllerContext originalContext) {
|
||||
assignControllerNumberIfNeeded(originalContext);
|
||||
|
||||
@ -1685,13 +1754,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
||||
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
|
||||
|
||||
deviceContext.leftTriggerMotor = leftTrigger;
|
||||
deviceContext.rightTriggerMotor = rightTrigger;
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
deviceContext.leftTriggerMotor = leftTrigger;
|
||||
deviceContext.rightTriggerMotor = rightTrigger;
|
||||
|
||||
if (deviceContext.controllerNumber == controllerNumber && deviceContext.quadVibrators) {
|
||||
rumbleQuadVibrators(deviceContext.vibratorManager,
|
||||
deviceContext.lowFreqMotor, deviceContext.highFreqMotor,
|
||||
deviceContext.leftTriggerMotor, deviceContext.rightTriggerMotor);
|
||||
if (deviceContext.quadVibrators) {
|
||||
rumbleQuadVibrators(deviceContext.vibratorManager,
|
||||
deviceContext.lowFreqMotor, deviceContext.highFreqMotor,
|
||||
deviceContext.leftTriggerMotor, deviceContext.rightTriggerMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1791,6 +1862,36 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSetControllerLED(short controllerNumber, byte r, byte g, byte b) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
for (int i = 0; i < inputDeviceContexts.size(); i++) {
|
||||
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
|
||||
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
// Create a new light session if one doesn't already exist
|
||||
if (deviceContext.lightsSession == null) {
|
||||
deviceContext.lightsSession = deviceContext.inputDevice.getLightsManager().openSession();
|
||||
}
|
||||
|
||||
// Convert the RGB components into the integer value that LightState uses
|
||||
int argbValue = 0xFF000000 | ((r << 16) & 0xFF0000) | ((g << 8) & 0xFF00) | (b & 0xFF);
|
||||
LightState lightState = new LightState.Builder().setColor(argbValue).build();
|
||||
|
||||
// Set the RGB value for each RGB-controllable LED on the device
|
||||
LightsRequest.Builder lightsRequestBuilder = new LightsRequest.Builder();
|
||||
for (Light light : deviceContext.inputDevice.getLightsManager().getLights()) {
|
||||
if (light.hasRgbControl()) {
|
||||
lightsRequestBuilder.addLight(light, lightState);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the LED changes
|
||||
deviceContext.lightsSession.requestLights(lightsRequestBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleButtonUp(KeyEvent event) {
|
||||
InputDeviceContext context = getContextForEvent(event);
|
||||
if (context == null) {
|
||||
@ -2362,6 +2463,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
public InputDevice inputDevice;
|
||||
|
||||
public LightsManager.LightsSession lightsSession;
|
||||
|
||||
// These are BatteryState values, not Moonlight values
|
||||
public int lastReportedBatteryStatus;
|
||||
public float lastReportedBatteryCapacity;
|
||||
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
|
||||
@ -2406,6 +2513,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
public long startDownTime = 0;
|
||||
|
||||
public final Runnable batteryStateUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendControllerBatteryPacket(InputDeviceContext.this);
|
||||
|
||||
// Requeue the callback
|
||||
handler.postDelayed(this, BATTERY_RECHECK_INTERVAL_MS);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
@ -2424,7 +2541,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (accelListener != null) {
|
||||
inputDevice.getSensorManager().unregisterListener(accelListener);
|
||||
}
|
||||
|
||||
if (lightsSession != null) {
|
||||
lightsSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
handler.removeCallbacks(batteryStateUpdateRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -2484,6 +2607,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (inputDevice.getSensorManager().getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
|
||||
capabilities |= MoonBridge.LI_CCAP_GYRO;
|
||||
}
|
||||
|
||||
if (inputDevice.getBatteryState().isPresent()) {
|
||||
capabilities |= MoonBridge.LI_CCAP_BATTERY_STATE;
|
||||
}
|
||||
|
||||
for (Light light : inputDevice.getLightsManager().getLights()) {
|
||||
if (light.hasRgbControl()) {
|
||||
capabilities |= MoonBridge.LI_CCAP_RGB_LED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputDevice.getVibrator().hasVibrator()) {
|
||||
@ -2499,6 +2632,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
conn.sendControllerArrivalEvent((byte)controllerNumber, getActiveControllerMask(),
|
||||
type, supportedButtonFlags, capabilities);
|
||||
|
||||
// After reporting arrival to the host, send initial battery state and begin monitoring
|
||||
sendControllerBatteryPacket(this);
|
||||
handler.postDelayed(batteryStateUpdateRunnable, BATTERY_RECHECK_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,6 +571,10 @@ public class NvConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendControllerBatteryEvent(byte controllerNumber, byte batteryState, byte batteryPercentage) {
|
||||
MoonBridge.sendControllerBatteryEvent(controllerNumber, batteryState, batteryPercentage);
|
||||
}
|
||||
|
||||
public void sendUtf8Text(final String text) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendUtf8Text(text);
|
||||
|
@ -18,4 +18,6 @@ public interface NvConnectionListener {
|
||||
void setHdrMode(boolean enabled, byte[] hdrMetadata);
|
||||
|
||||
void setMotionEventState(short controllerNumber, byte motionType, short reportRateHz);
|
||||
|
||||
void setControllerLED(short controllerNumber, byte r, byte g, byte b);
|
||||
}
|
||||
|
@ -107,10 +107,21 @@ public class MoonBridge {
|
||||
public static final short LI_CCAP_TOUCHPAD = 0x08;
|
||||
public static final short LI_CCAP_ACCEL = 0x10;
|
||||
public static final short LI_CCAP_GYRO = 0x20;
|
||||
public static final short LI_CCAP_BATTERY_STATE = 0x40;
|
||||
public static final short LI_CCAP_RGB_LED = 0x80;
|
||||
|
||||
public static final byte LI_MOTION_TYPE_ACCEL = 0x01;
|
||||
public static final byte LI_MOTION_TYPE_GYRO = 0x02;
|
||||
|
||||
public static final byte LI_BATTERY_STATE_UNKNOWN = 0x00;
|
||||
public static final byte LI_BATTERY_STATE_NOT_PRESENT = 0x01;
|
||||
public static final byte LI_BATTERY_STATE_DISCHARGING = 0x02;
|
||||
public static final byte LI_BATTERY_STATE_CHARGING = 0x03;
|
||||
public static final byte LI_BATTERY_STATE_NOT_CHARGING = 0x04; // Connected to power but not charging
|
||||
public static final byte LI_BATTERY_STATE_FULL = 0x05;
|
||||
|
||||
public static final byte LI_BATTERY_PERCENTAGE_UNKNOWN = (byte)0xFF;
|
||||
|
||||
private static AudioRenderer audioRenderer;
|
||||
private static VideoDecoderRenderer videoRenderer;
|
||||
private static NvConnectionListener connectionListener;
|
||||
@ -307,6 +318,12 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bridgeClSetControllerLED(short controllerNumber, byte r, byte g, byte b) {
|
||||
if (connectionListener != null) {
|
||||
connectionListener.setControllerLED(controllerNumber, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupBridge(VideoDecoderRenderer videoRenderer, AudioRenderer audioRenderer, NvConnectionListener connectionListener) {
|
||||
MoonBridge.videoRenderer = videoRenderer;
|
||||
MoonBridge.audioRenderer = audioRenderer;
|
||||
@ -361,6 +378,8 @@ public class MoonBridge {
|
||||
|
||||
public static native int sendControllerMotionEvent(byte controllerNumber, byte motionType, float x, float y, float z);
|
||||
|
||||
public static native int sendControllerBatteryEvent(byte controllerNumber, byte batteryState, byte batteryPercentage);
|
||||
|
||||
public static native void sendKeyboardInput(short keyMap, byte keyDirection, byte modifier, byte flags);
|
||||
|
||||
public static native void sendMouseHighResScroll(short scrollAmount);
|
||||
|
@ -35,6 +35,7 @@ static jmethodID BridgeClConnectionStatusUpdateMethod;
|
||||
static jmethodID BridgeClSetHdrModeMethod;
|
||||
static jmethodID BridgeClRumbleTriggersMethod;
|
||||
static jmethodID BridgeClSetMotionEventStateMethod;
|
||||
static jmethodID BridgeClSetControllerLEDMethod;
|
||||
static jbyteArray DecodedFrameBuffer;
|
||||
static jshortArray DecodedAudioBuffer;
|
||||
|
||||
@ -98,6 +99,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z[B)V");
|
||||
BridgeClRumbleTriggersMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumbleTriggers", "(SSS)V");
|
||||
BridgeClSetMotionEventStateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetMotionEventState", "(SBS)V");
|
||||
BridgeClSetControllerLEDMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetControllerLED", "(SBBB)V");
|
||||
}
|
||||
|
||||
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
@ -374,6 +376,17 @@ void BridgeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType,
|
||||
}
|
||||
}
|
||||
|
||||
void BridgeClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b) {
|
||||
JNIEnv* env = GetThreadEnv();
|
||||
|
||||
// These jbyte casts are necessary to satisfy CheckJNI
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetControllerLEDMethod, controllerNumber, (jbyte)r, (jbyte)g, (jbyte)b);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
}
|
||||
}
|
||||
|
||||
void BridgeClLogMessage(const char* format, ...) {
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
@ -410,6 +423,7 @@ static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
|
||||
.setHdrMode = BridgeClSetHdrMode,
|
||||
.rumbleTriggers = BridgeClRumbleTriggers,
|
||||
.setMotionEventState = BridgeClSetMotionEventState,
|
||||
.setControllerLED = BridgeClSetControllerLED,
|
||||
};
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit c5dc45e1443363d95b9708de26e86ed57b6946e6
|
||||
Subproject commit c0792168f5a7ed48fc6feeb7fce01b83df405df2
|
@ -80,6 +80,14 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendControllerMotionEvent(JNIEnv *env
|
||||
return LiSendControllerMotionEvent(controllerNumber, motionType, x, y, z);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendControllerBatteryEvent(JNIEnv *env, jclass clazz,
|
||||
jbyte controllerNumber,
|
||||
jbyte batteryState,
|
||||
jbyte batteryPercentage) {
|
||||
return LiSendControllerBatteryEvent(controllerNumber, batteryState, batteryPercentage);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendKeyboardInput(JNIEnv *env, jclass clazz, jshort keyCode, jbyte keyAction, jbyte modifiers, jbyte flags) {
|
||||
LiSendKeyboardEvent2(keyCode, keyAction, modifiers, flags);
|
||||
|
Loading…
x
Reference in New Issue
Block a user