Compare commits

..
16 Commits
15 changed files with 170 additions and 23 deletions

No files matched your search

+5 -5
View File
@@ -7,8 +7,8 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionName "9.7.2" versionName "9.7.6"
versionCode = 242 versionCode = 246
} }
flavorDimensions "root" flavorDimensions "root"
@@ -114,10 +114,10 @@ android {
} }
dependencies { dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.64' implementation 'org.bouncycastle:bcprov-jdk15on:1.66'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64' implementation 'org.bouncycastle:bcpkix-jdk15on:1.66'
implementation 'org.jcodec:jcodec:0.2.3' implementation 'org.jcodec:jcodec:0.2.3'
implementation 'com.squareup.okhttp3:okhttp:3.12.10' implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.squareup.okio:okio:1.17.5' implementation 'com.squareup.okio:okio:1.17.5'
implementation 'org.jmdns:jmdns:3.5.5' implementation 'org.jmdns:jmdns:3.5.5'
} }
+9
View File
@@ -126,6 +126,15 @@
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.AppView" /> android:value="com.limelight.AppView" />
<!-- Special metadata for NVIDIA Shield devices to prevent input buffering
and most importantly, opt out of mouse acceleration while streaming -->
<meta-data
android:name="com.nvidia.immediateInput"
android:value="true" />
<meta-data
android:name="com.nvidia.rawCursorInput"
android:value="true" />
</activity> </activity>
<service <service
@@ -40,6 +40,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25; private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
private static final int EMULATING_SPECIAL = 0x1;
private static final int EMULATING_SELECT = 0x2;
private static final int EMULATED_SPECIAL_UP_DELAY_MS = 100;
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
private final Vector2d inputVector = new Vector2d(); private final Vector2d inputVector = new Vector2d();
private final SparseArray<InputDeviceContext> inputDeviceContexts = new SparseArray<>(); private final SparseArray<InputDeviceContext> inputDeviceContexts = new SparseArray<>();
@@ -461,6 +467,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.vibrator = dev.getVibrator(); context.vibrator = dev.getVibrator();
} }
// Detect if the gamepad has Mode and Select buttons according to the Android key layouts.
// We do this first because other codepaths below may override these defaults.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
boolean[] buttons = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_MODE, KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BACK, 0);
context.hasMode = buttons[0];
context.hasSelect = buttons[1] || buttons[2];
}
context.leftStickXAxis = MotionEvent.AXIS_X; context.leftStickXAxis = MotionEvent.AXIS_X;
context.leftStickYAxis = MotionEvent.AXIS_Y; context.leftStickYAxis = MotionEvent.AXIS_Y;
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null && if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
@@ -519,6 +533,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// The old DS4 driver uses RX and RY for triggers // The old DS4 driver uses RX and RY for triggers
context.leftTriggerAxis = MotionEvent.AXIS_RX; context.leftTriggerAxis = MotionEvent.AXIS_RX;
context.rightTriggerAxis = MotionEvent.AXIS_RY; context.rightTriggerAxis = MotionEvent.AXIS_RY;
// DS4 has Select and Mode buttons (possibly mapped non-standard)
context.hasSelect = true;
context.hasMode = true;
} }
else { else {
// If it's not a non-standard DS4 controller, it's probably an Xbox controller or // If it's not a non-standard DS4 controller, it's probably an Xbox controller or
@@ -600,6 +618,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.backIsStart = true; context.backIsStart = true;
context.modeIsSelect = true; context.modeIsSelect = true;
context.triggerDeadzone = 0.30f; context.triggerDeadzone = 0.30f;
context.hasSelect = true;
context.hasMode = false;
} }
} }
@@ -617,6 +637,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (!hasStartKey[0] && !hasStartKey[1]) { if (!hasStartKey[0] && !hasStartKey[1]) {
context.backIsStart = true; context.backIsStart = true;
context.modeIsSelect = true; context.modeIsSelect = true;
context.hasSelect = true;
context.hasMode = false;
} }
} }
@@ -625,14 +647,26 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.triggerDeadzone = 0.30f; context.triggerDeadzone = 0.30f;
} }
// SHIELD controllers will use small stick deadzones // SHIELD controllers will use small stick deadzones
else if (devName.contains("SHIELD")) { else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
context.leftStickDeadzoneRadius = 0.07f; context.leftStickDeadzoneRadius = 0.07f;
context.rightStickDeadzoneRadius = 0.07f; context.rightStickDeadzoneRadius = 0.07f;
// The big Nvidia button on the Shield controllers acts like a Search button. It
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
// nothing, so we can hijack it to act like a mode button.
if (devName.contains("NVIDIA Controller v01.03") || devName.contains("NVIDIA Controller v01.04")) {
context.searchIsMode = true;
context.hasMode = true;
}
} }
// The Serval has a couple of unknown buttons that are start and select. It also has // The Serval has a couple of unknown buttons that are start and select. It also has
// a back button which we want to ignore since there's already a select button. // a back button which we want to ignore since there's already a select button.
else if (devName.contains("Razer Serval")) { else if (devName.contains("Razer Serval")) {
context.isServal = true; context.isServal = true;
// Serval has Select and Mode buttons (possibly mapped non-standard)
context.hasMode = true;
context.hasSelect = true;
} }
// The Xbox One S Bluetooth controller has some mappings that need fixing up. // The Xbox One S Bluetooth controller has some mappings that need fixing up.
// However, Microsoft released a firmware update with no change to VID/PID // However, Microsoft released a firmware update with no change to VID/PID
@@ -643,6 +677,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
else if (devName.equals("Xbox Wireless Controller")) { else if (devName.equals("Xbox Wireless Controller")) {
if (gasRange == null) { if (gasRange == null) {
context.isNonStandardXboxBtController = true; context.isNonStandardXboxBtController = true;
// Xbox One S has Select and Mode buttons (possibly mapped non-standard)
context.hasMode = true;
context.hasSelect = true;
} }
} }
} }
@@ -1018,6 +1056,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Emulate the select button with mode // Emulate the select button with mode
return KeyEvent.KEYCODE_BUTTON_SELECT; return KeyEvent.KEYCODE_BUTTON_SELECT;
} }
else if (context.searchIsMode && keyCode == KeyEvent.KEYCODE_SEARCH) {
// Emulate the mode button with search
return KeyEvent.KEYCODE_BUTTON_MODE;
}
return keyCode; return keyCode;
} }
@@ -1416,6 +1458,41 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return false; return false;
} }
// Check if we're emulating the select button
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SELECT) != 0)
{
// If either start or LB is up, select comes up too
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
(context.inputMap & ControllerPacket.LB_FLAG) == 0)
{
context.inputMap &= ~ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SELECT;
try {
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
}
}
// Check if we're emulating the special button
if ((context.emulatingButtonFlags & ControllerHandler.EMULATING_SPECIAL) != 0)
{
// If either start or select and RB is up, the special button comes up too
if ((context.inputMap & ControllerPacket.PLAY_FLAG) == 0 ||
((context.inputMap & ControllerPacket.BACK_FLAG) == 0 &&
(context.inputMap & ControllerPacket.RB_FLAG) == 0))
{
context.inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags &= ~ControllerHandler.EMULATING_SPECIAL;
try {
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
}
}
sendControllerInputPacket(context); sendControllerInputPacket(context);
if (context.pendingExit && context.inputMap == 0) { if (context.pendingExit && context.inputMap == 0) {
@@ -1444,6 +1521,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE: case KeyEvent.KEYCODE_BUTTON_MODE:
context.hasMode = true;
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
break; break;
case KeyEvent.KEYCODE_BUTTON_START: case KeyEvent.KEYCODE_BUTTON_START:
@@ -1455,6 +1533,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
break; break;
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT: case KeyEvent.KEYCODE_BUTTON_SELECT:
context.hasSelect = true;
context.inputMap |= ControllerPacket.BACK_FLAG; context.inputMap |= ControllerPacket.BACK_FLAG;
break; break;
case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -1535,6 +1614,43 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.pendingExit = true; context.pendingExit = true;
} }
// Start+LB acts like select for controllers with one button
if (!context.hasSelect) {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
context.inputMap |= ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
}
}
// If there is a physical select button, we'll use Start+Select as the special button combo
// otherwise we'll use Start+RB.
if (!context.hasMode) {
if (context.hasSelect) {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG)) {
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
}
else {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
}
}
// We don't need to send repeat key down events, but the platform // We don't need to send repeat key down events, but the platform
// sends us events that claim to be repeats but they're from different // sends us events that claim to be repeats but they're from different
// devices, so we just send them all and deal with some duplicates. // devices, so we just send them all and deal with some duplicates.
@@ -1676,10 +1792,15 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public boolean isServal; public boolean isServal;
public boolean backIsStart; public boolean backIsStart;
public boolean modeIsSelect; public boolean modeIsSelect;
public boolean searchIsMode;
public boolean ignoreBack; public boolean ignoreBack;
public boolean hasJoystickAxes; public boolean hasJoystickAxes;
public boolean pendingExit; public boolean pendingExit;
public int emulatingButtonFlags = 0;
public boolean hasSelect;
public boolean hasMode;
// Used for OUYA bumper state tracking since they force all buttons // Used for OUYA bumper state tracking since they force all buttons
// up when the OUYA button goes down. We watch the last time we get // up when the OUYA button goes down. We watch the last time we get
// a bumper up and compare that to our maximum delay when we receive // a bumper up and compare that to our maximum delay when we receive
@@ -8,6 +8,7 @@ import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.SystemClock;
import android.view.MotionEvent; import android.view.MotionEvent;
import java.util.ArrayList; import java.util.ArrayList;
@@ -270,7 +271,7 @@ public class AnalogStick extends VirtualControllerElement {
// We also release the deadzone if the user keeps the stick pressed for a bit to allow // We also release the deadzone if the user keeps the stick pressed for a bit to allow
// them to make precise movements. // them to make precise movements.
stick_state = (stick_state == STICK_STATE.MOVED_ACTIVE || stick_state = (stick_state == STICK_STATE.MOVED_ACTIVE ||
System.currentTimeMillis() - timeLastClick > timeoutDeadzone || SystemClock.uptimeMillis() - timeLastClick > timeoutDeadzone ||
movement_radius > radius_dead_zone) ? movement_radius > radius_dead_zone) ?
STICK_STATE.MOVED_ACTIVE : STICK_STATE.MOVED_IN_DEAD_ZONE; STICK_STATE.MOVED_ACTIVE : STICK_STATE.MOVED_IN_DEAD_ZONE;
@@ -311,7 +312,7 @@ public class AnalogStick extends VirtualControllerElement {
stick_state = STICK_STATE.MOVED_IN_DEAD_ZONE; stick_state = STICK_STATE.MOVED_IN_DEAD_ZONE;
// check for double click // check for double click
if (lastClickState == CLICK_STATE.SINGLE && if (lastClickState == CLICK_STATE.SINGLE &&
timeLastClick + timeoutDoubleClick > System.currentTimeMillis()) { timeLastClick + timeoutDoubleClick > SystemClock.uptimeMillis()) {
click_state = CLICK_STATE.DOUBLE; click_state = CLICK_STATE.DOUBLE;
notifyOnDoubleClick(); notifyOnDoubleClick();
} else { } else {
@@ -319,7 +320,7 @@ public class AnalogStick extends VirtualControllerElement {
notifyOnClick(); notifyOnClick();
} }
// reset last click timestamp // reset last click timestamp
timeLastClick = System.currentTimeMillis(); timeLastClick = SystemClock.uptimeMillis();
// set item pressed and update // set item pressed and update
setPressed(true); setPressed(true);
break; break;
@@ -20,6 +20,7 @@ import android.media.MediaFormat;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException; import android.media.MediaCodec.CodecException;
import android.os.Build; import android.os.Build;
import android.os.SystemClock;
import android.util.Range; import android.util.Range;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@@ -410,7 +411,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// //
if (initialException != null) { if (initialException != null) {
// This isn't the first time we've had an exception processing video // This isn't the first time we've had an exception processing video
if (System.currentTimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) { if (SystemClock.uptimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) {
// It's been over 3 seconds and we're still getting exceptions. Throw the original now. // It's been over 3 seconds and we're still getting exceptions. Throw the original now.
if (!reportedCrash) { if (!reportedCrash) {
reportedCrash = true; reportedCrash = true;
@@ -427,7 +428,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
else { else {
initialException = new RendererException(this, e); initialException = new RendererException(this, e);
} }
initialExceptionTimestamp = System.currentTimeMillis(); initialExceptionTimestamp = SystemClock.uptimeMillis();
} }
} }
} }
@@ -637,7 +638,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
} }
if (lastFrameNumber == 0) { if (lastFrameNumber == 0) {
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis(); activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
} else if (frameNumber != lastFrameNumber && frameNumber != lastFrameNumber + 1) { } else if (frameNumber != lastFrameNumber && frameNumber != lastFrameNumber + 1) {
// We can receive the same "frame" multiple times if it's an IDR frame. // We can receive the same "frame" multiple times if it's an IDR frame.
// In that case, each frame start NALU is submitted independently. // In that case, each frame start NALU is submitted independently.
@@ -649,7 +650,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
lastFrameNumber = frameNumber; lastFrameNumber = frameNumber;
// Flip stats windows roughly every second // Flip stats windows roughly every second
if (System.currentTimeMillis() >= activeWindowVideoStats.measurementStartTimestamp + 1000) { if (SystemClock.uptimeMillis() >= activeWindowVideoStats.measurementStartTimestamp + 1000) {
if (prefs.enablePerfOverlay) { if (prefs.enablePerfOverlay) {
VideoStats lastTwo = new VideoStats(); VideoStats lastTwo = new VideoStats();
lastTwo.add(lastWindowVideoStats); lastTwo.add(lastWindowVideoStats);
@@ -682,7 +683,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
globalVideoStats.add(activeWindowVideoStats); globalVideoStats.add(activeWindowVideoStats);
lastWindowVideoStats.copy(activeWindowVideoStats); lastWindowVideoStats.copy(activeWindowVideoStats);
activeWindowVideoStats.clear(); activeWindowVideoStats.clear();
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis(); activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
} }
activeWindowVideoStats.totalFramesReceived++; activeWindowVideoStats.totalFramesReceived++;
@@ -153,9 +153,15 @@ public class MediaCodecHelper {
whitelistedHevcDecoders.add("omx.mtk"); whitelistedHevcDecoders.add("omx.mtk");
} }
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
whitelistedHevcDecoders.add("omx.amlogic");
}
// These theoretically have good HEVC decoding capabilities (potentially better than // These theoretically have good HEVC decoding capabilities (potentially better than
// their AVC decoders), but haven't been tested enough // their AVC decoders), but haven't been tested enough
//whitelistedHevcDecoders.add("omx.amlogic");
//whitelistedHevcDecoders.add("omx.rk"); //whitelistedHevcDecoders.add("omx.rk");
// Let's see if HEVC decoders are finally stable with C2 // Let's see if HEVC decoders are finally stable with C2
@@ -1,5 +1,7 @@
package com.limelight.binding.video; package com.limelight.binding.video;
import android.os.SystemClock;
class VideoStats { class VideoStats {
long decoderTimeMs; long decoderTimeMs;
@@ -24,7 +26,7 @@ class VideoStats {
this.measurementStartTimestamp = other.measurementStartTimestamp; this.measurementStartTimestamp = other.measurementStartTimestamp;
} }
assert other.measurementStartTimestamp <= this.measurementStartTimestamp; assert other.measurementStartTimestamp >= this.measurementStartTimestamp;
} }
void copy(VideoStats other) { void copy(VideoStats other) {
@@ -50,7 +52,7 @@ class VideoStats {
} }
VideoStatsFps getFps() { VideoStatsFps getFps() {
float elapsed = (System.currentTimeMillis() - this.measurementStartTimestamp) / (float) 1000; float elapsed = (SystemClock.uptimeMillis() - this.measurementStartTimestamp) / (float) 1000;
VideoStatsFps fps = new VideoStatsFps(); VideoStatsFps fps = new VideoStatsFps();
if (elapsed > 0) { if (elapsed > 0) {
@@ -40,6 +40,7 @@ import android.net.NetworkCapabilities;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@@ -176,7 +177,7 @@ public class ComputerManagerService extends Service {
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")"); LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
offlineCount++; offlineCount++;
} else { } else {
tuple.lastSuccessfulPollMs = System.currentTimeMillis(); tuple.lastSuccessfulPollMs = SystemClock.elapsedRealtime();
offlineCount = 0; offlineCount = 0;
} }
} }
@@ -207,7 +208,7 @@ public class ComputerManagerService extends Service {
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
// Enforce the poll data TTL // Enforce the poll data TTL
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) { if (SystemClock.elapsedRealtime() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
LimeLog.info("Timing out polled state for "+tuple.computer.name); LimeLog.info("Timing out polled state for "+tuple.computer.name);
tuple.computer.state = ComputerDetails.State.UNKNOWN; tuple.computer.state = ComputerDetails.State.UNKNOWN;
} }
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:4.1.0'
} }
} }
@@ -0,0 +1 @@
- Improved connection reliability
@@ -0,0 +1 @@
- Improved performance on Google TV Chromecast and other newer Amlogic devices by using HEVC by default
@@ -0,0 +1,3 @@
- Gamepad button combos to emulate missing Select and Guide buttons have been reintroduced
- For gamepads without a Select button, LB+Start will trigger the Select button and RB+Start will trigger the Guide button
- For gamepads with a Select button, Start+Select will trigger the Guide button
@@ -0,0 +1 @@
- Enabled raw mouse input on the Nvidia Shield TV to avoid mouse acceleration
+2 -2
View File
@@ -1,6 +1,6 @@
#Thu May 28 11:41:09 PDT 2020 #Mon Oct 12 13:42:18 CDT 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip