Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b16676b54a | |||
| dc9bfe5189 | |||
| 20039a422e | |||
| 22b9c9ca68 | |||
| 0c546e35ec | |||
| b70370ac09 | |||
| aa10bb7dc5 | |||
| c6100a9be1 | |||
| 529a2f7bf8 | |||
| 9ec7e916c5 | |||
| 982b36cf98 | |||
| a73eab5e92 | |||
| a8479ccb5f | |||
| f55e4e0e01 | |||
| d08c32ce04 | |||
| 1d599c5e60 | |||
| e888ae59e4 | |||
| 951d544894 | |||
| 49898b34e1 | |||
| 3854a6a42e | |||
| d4da5bc281 | |||
| 04954f5242 | |||
| 9fc5496526 | |||
| e363d24b1c | |||
| 801f4027a2 | |||
| c0dc344f76 | |||
| b5b3d81f00 | |||
| 8dd8dbc1d1 | |||
| 8f31aa59a8 | |||
| 5b581b6c0f | |||
| 297ac64fde | |||
| d4490f0e17 | |||
| d04e7a3231 | |||
| 5b456aba27 | |||
| 0c065dcc1f | |||
| 531f73329d | |||
| d6ba72032d | |||
| bfdc7a2609 | |||
| 031abf03da | |||
| 6aac8e6be6 | |||
| 8ff93d21c3 | |||
| 6df3d0bc44 | |||
| 0b18e8fdb4 | |||
| 19d8ae0f78 | |||
| d7ffb5dddc | |||
| 2859b73dfe | |||
| 6f9021a5e6 | |||
| 3bfeaefdbd | |||
| db1eace975 | |||
| 2f9ae107a2 |
+18
-12
@@ -1,25 +1,28 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
ndkVersion "23.1.7779620"
|
ndkVersion "23.2.8568313"
|
||||||
|
|
||||||
compileSdkVersion 32
|
compileSdk 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdk 16
|
||||||
targetSdkVersion 32
|
targetSdk 33
|
||||||
|
|
||||||
versionName "10.2"
|
versionName "10.4"
|
||||||
versionCode = 275
|
versionCode = 280
|
||||||
|
|
||||||
|
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||||
|
ndk.debugSymbolLevel = 'FULL'
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "root"
|
flavorDimensions.add("root")
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
root {
|
root {
|
||||||
// Android O has native mouse capture, so don't show the rooted
|
// Android O has native mouse capture, so don't show the rooted
|
||||||
// version to devices running O on the Play Store.
|
// version to devices running O on the Play Store.
|
||||||
maxSdkVersion 25
|
maxSdk 25
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
@@ -29,6 +32,7 @@ android {
|
|||||||
|
|
||||||
applicationId "com.limelight.root"
|
applicationId "com.limelight.root"
|
||||||
dimension "root"
|
dimension "root"
|
||||||
|
buildConfigField "boolean", "ROOT_BUILD", "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
nonRoot {
|
nonRoot {
|
||||||
@@ -40,6 +44,7 @@ android {
|
|||||||
|
|
||||||
applicationId "com.limelight"
|
applicationId "com.limelight"
|
||||||
dimension "root"
|
dimension "root"
|
||||||
|
buildConfigField "boolean", "ROOT_BUILD", "false"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +60,7 @@ android {
|
|||||||
enableSplit = false
|
enableSplit = false
|
||||||
}
|
}
|
||||||
density {
|
density {
|
||||||
// FIXME: This should not be neccessary but we get
|
// FIXME: This should not be necessary but we get
|
||||||
// weird crashes due to missing drawable resources
|
// weird crashes due to missing drawable resources
|
||||||
// when this split is enabled.
|
// when this split is enabled.
|
||||||
enableSplit = false
|
enableSplit = false
|
||||||
@@ -65,6 +70,8 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
|
resValue "string", "app_label", "Moonlight (Debug)"
|
||||||
|
resValue "string", "app_label_root", "Moonlight (Root Debug)"
|
||||||
|
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
@@ -101,6 +108,8 @@ android {
|
|||||||
//
|
//
|
||||||
// TL;DR: Leave the following line alone!
|
// TL;DR: Leave the following line alone!
|
||||||
applicationIdSuffix ".unofficial"
|
applicationIdSuffix ".unofficial"
|
||||||
|
resValue "string", "app_label", "Moonlight"
|
||||||
|
resValue "string", "app_label_root", "Moonlight (Root)"
|
||||||
|
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
@@ -112,9 +121,6 @@ android {
|
|||||||
path "src/main/jni/Android.mk"
|
path "src/main/jni/Android.mk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
|
||||||
android.defaultConfig.ndk.debugSymbolLevel = 'FULL'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<string name="app_label" translatable="false">Moonlight (Debug)</string>
|
|
||||||
<string name="app_label_root" translatable="false">Moonlight (Root Debug)</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
@@ -45,21 +45,28 @@
|
|||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:gwpAsanMode="always"
|
android:gwpAsanMode="always"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".PosterContentProvider"
|
android:name=".PosterContentProvider"
|
||||||
android:authorities="poster.${applicationId}"
|
android:authorities="poster.${applicationId}"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<!-- Samsung multi-window support -->
|
<!-- Samsung multi-window support -->
|
||||||
<uses-library
|
<uses-library
|
||||||
android:name="com.sec.android.app.multiwindow"
|
android:name="com.sec.android.app.multiwindow"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.sec.android.support.multiwindow"
|
android:name="com.sec.android.support.multiwindow"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
|
<!-- Disable Game Mode downscaling since it can break our UI dialogs and doesn't benefit
|
||||||
|
performance much for us since we don't use GL/Vulkan for rendering anyway -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.android.graphics.intervention.wm.allowDownscale"
|
||||||
|
android:value="false"/>
|
||||||
|
|
||||||
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
||||||
in each activity even though it is implied by targeting API 24+ -->
|
in each activity even though it is implied by targeting API 24+ -->
|
||||||
|
|
||||||
|
|||||||
@@ -293,6 +293,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_app_view);
|
setContentView(R.layout.activity_app_view);
|
||||||
|
|
||||||
|
// Allow floating expanded PiP overlays while browsing apps
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
setShouldDockBigOverlays(false);
|
||||||
|
}
|
||||||
|
|
||||||
UiHelper.notifyNewRootView(this);
|
UiHelper.notifyNewRootView(this);
|
||||||
|
|
||||||
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ import java.util.Locale;
|
|||||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||||
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
|
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
|
||||||
PerfOverlayListener
|
PerfOverlayListener, UsbDriverService.UsbDriverStateListener
|
||||||
{
|
{
|
||||||
private int lastButtonState = 0;
|
private int lastButtonState = 0;
|
||||||
|
|
||||||
@@ -107,6 +107,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
||||||
|
|
||||||
private ControllerHandler controllerHandler;
|
private ControllerHandler controllerHandler;
|
||||||
|
private KeyboardTranslator keyboardTranslator;
|
||||||
private VirtualController virtualController;
|
private VirtualController virtualController;
|
||||||
|
|
||||||
private PreferenceConfiguration prefConfig;
|
private PreferenceConfiguration prefConfig;
|
||||||
@@ -120,6 +121,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
private boolean autoEnterPip = false;
|
private boolean autoEnterPip = false;
|
||||||
private boolean surfaceCreated = false;
|
private boolean surfaceCreated = false;
|
||||||
private boolean attemptedConnection = false;
|
private boolean attemptedConnection = false;
|
||||||
|
private int suppressPipRefCount = 0;
|
||||||
|
private String pcName;
|
||||||
|
private String appName;
|
||||||
|
|
||||||
private InputCaptureProvider inputCaptureProvider;
|
private InputCaptureProvider inputCaptureProvider;
|
||||||
private int modifierFlags = 0;
|
private int modifierFlags = 0;
|
||||||
@@ -150,6 +154,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
||||||
binder.setListener(controllerHandler);
|
binder.setListener(controllerHandler);
|
||||||
|
binder.setStateListener(Game.this);
|
||||||
|
binder.start();
|
||||||
connectedToUsbDriverService = true;
|
connectedToUsbDriverService = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,12 +275,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
lowLatencyWifiLock.acquire();
|
lowLatencyWifiLock.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
||||||
|
pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||||
|
|
||||||
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
||||||
String appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
|
||||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||||
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
|
||||||
boolean appSupportsHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
boolean appSupportsHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
||||||
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
|
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
|
||||||
|
|
||||||
@@ -440,9 +447,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Initialize the connection
|
// Initialize the connection
|
||||||
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
|
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
|
||||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
||||||
|
keyboardTranslator = new KeyboardTranslator();
|
||||||
|
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||||
|
inputManager.registerInputDeviceListener(keyboardTranslator, null);
|
||||||
|
|
||||||
// Initialize touch contexts
|
// Initialize touch contexts
|
||||||
for (int i = 0; i < touchContextMap.length; i++) {
|
for (int i = 0; i < touchContextMap.length; i++) {
|
||||||
@@ -513,6 +522,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
performanceOverlayView.setVisibility(View.GONE);
|
performanceOverlayView.setVisibility(View.GONE);
|
||||||
notificationOverlayView.setVisibility(View.GONE);
|
notificationOverlayView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're in PiP (still gaming, but interruptible)
|
||||||
|
UiHelper.notifyStreamEnteringPiP(this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
isHidingOverlays = false;
|
isHidingOverlays = false;
|
||||||
@@ -528,6 +540,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
notificationOverlayView.setVisibility(requestedNotificationOverlayVisibility);
|
notificationOverlayView.setVisibility(requestedNotificationOverlayVisibility);
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're out of PiP (gaming, non-interruptible)
|
||||||
|
UiHelper.notifyStreamExitingPiP(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,14 +561,28 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
builder.setSeamlessResizeEnabled(true);
|
builder.setSeamlessResizeEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (appName != null) {
|
||||||
|
builder.setTitle(appName);
|
||||||
|
if (pcName != null) {
|
||||||
|
builder.setSubtitle(pcName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pcName != null) {
|
||||||
|
builder.setTitle(pcName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPipAutoEnter(boolean autoEnter) {
|
private void updatePipAutoEnter() {
|
||||||
if (!prefConfig.enablePip) {
|
if (!prefConfig.enablePip) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean autoEnter = connected && suppressPipRefCount == 0;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
setPictureInPictureParams(getPictureInPictureParams(autoEnter));
|
setPictureInPictureParams(getPictureInPictureParams(autoEnter));
|
||||||
}
|
}
|
||||||
@@ -865,10 +894,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
|
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||||
if (controllerHandler != null) {
|
if (controllerHandler != null) {
|
||||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
|
||||||
inputManager.unregisterInputDeviceListener(controllerHandler);
|
inputManager.unregisterInputDeviceListener(controllerHandler);
|
||||||
}
|
}
|
||||||
|
if (keyboardTranslator != null) {
|
||||||
|
inputManager.unregisterInputDeviceListener(keyboardTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
if (lowLatencyWifiLock != null) {
|
if (lowLatencyWifiLock != null) {
|
||||||
lowLatencyWifiLock.release();
|
lowLatencyWifiLock.release();
|
||||||
@@ -1088,7 +1120,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// Try the keyboard handler
|
// Try the keyboard handler
|
||||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||||
if (translated == 0) {
|
if (translated == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1158,7 +1190,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// Try the keyboard handler
|
// Try the keyboard handler
|
||||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||||
if (translated == 0) {
|
if (translated == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1428,7 +1460,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_POINTER_UP:
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
if (event.getPointerCount() == 1) {
|
if (event.getPointerCount() == 1 &&
|
||||||
|
(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || (event.getFlags() & MotionEvent.FLAG_CANCELED) == 0)) {
|
||||||
// All fingers up
|
// All fingers up
|
||||||
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
||||||
// This is a 3 finger tap to bring up the keyboard
|
// This is a 3 finger tap to bring up the keyboard
|
||||||
@@ -1436,7 +1469,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.touchUpEvent(eventX, eventY);
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && (event.getFlags() & MotionEvent.FLAG_CANCELED) != 0) {
|
||||||
|
context.cancelTouch();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.touchUpEvent(eventX, eventY);
|
||||||
|
}
|
||||||
|
|
||||||
for (TouchContext touchContext : touchContextMap) {
|
for (TouchContext touchContext : touchContextMap) {
|
||||||
touchContext.setPointerCount(event.getPointerCount() - 1);
|
touchContext.setPointerCount(event.getPointerCount() - 1);
|
||||||
}
|
}
|
||||||
@@ -1573,11 +1613,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
private void stopConnection() {
|
private void stopConnection() {
|
||||||
if (connecting || connected) {
|
if (connecting || connected) {
|
||||||
setPipAutoEnter(false);
|
|
||||||
connecting = connected = false;
|
connecting = connected = false;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
|
||||||
controllerHandler.stop();
|
controllerHandler.stop();
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're no longer in game
|
||||||
|
UiHelper.notifyStreamEnded(this);
|
||||||
|
|
||||||
// Stop may take a few hundred ms to do some network I/O to tell
|
// Stop may take a few hundred ms to do some network I/O to tell
|
||||||
// the server we're going away and clean up. Let it run in a separate
|
// the server we're going away and clean up. Let it run in a separate
|
||||||
// thread to keep things smooth for the UI. Inside moonlight-common,
|
// thread to keep things smooth for the UI. Inside moonlight-common,
|
||||||
@@ -1738,9 +1781,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
spinner = null;
|
spinner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPipAutoEnter(true);
|
|
||||||
connected = true;
|
connected = true;
|
||||||
connecting = false;
|
connecting = false;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
|
||||||
// Hide the mouse cursor now after a short delay.
|
// Hide the mouse cursor now after a short delay.
|
||||||
// Doing it before dismissing the spinner seems to be undone
|
// Doing it before dismissing the spinner seems to be undone
|
||||||
@@ -1758,6 +1801,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Keep the display on
|
// Keep the display on
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're in game
|
||||||
|
UiHelper.notifyStreamConnected(Game.this);
|
||||||
|
|
||||||
hideSystemUi(1000);
|
hideSystemUi(1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1807,6 +1853,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
if (!attemptedConnection) {
|
if (!attemptedConnection) {
|
||||||
attemptedConnection = true;
|
attemptedConnection = true;
|
||||||
|
|
||||||
|
// Update GameManager state to indicate we're "loading" while connecting
|
||||||
|
UiHelper.notifyStreamConnecting(Game.this);
|
||||||
|
|
||||||
decoderRenderer.setRenderTarget(holder);
|
decoderRenderer.setRenderTarget(holder);
|
||||||
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||||
}
|
}
|
||||||
@@ -1884,7 +1933,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||||
short keyMap = KeyboardTranslator.translate(keyCode);
|
short keyMap = keyboardTranslator.translate(keyCode, -1);
|
||||||
if (keyMap != 0) {
|
if (keyMap != 0) {
|
||||||
// handleSpecialKeys() takes the Android keycode
|
// handleSpecialKeys() takes the Android keycode
|
||||||
if (handleSpecialKeys(keyCode, buttonDown)) {
|
if (handleSpecialKeys(keyCode, buttonDown)) {
|
||||||
@@ -1932,4 +1981,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUsbPermissionPromptStarting() {
|
||||||
|
// Disable PiP auto-enter while the USB permission prompt is on-screen. This prevents
|
||||||
|
// us from entering PiP while the user is interacting with the OS permission dialog.
|
||||||
|
suppressPipRefCount++;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUsbPermissionPromptCompleted() {
|
||||||
|
suppressPipRefCount--;
|
||||||
|
updatePipAutoEnter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package com.limelight;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
import android.window.OnBackInvokedCallback;
|
||||||
|
import android.window.OnBackInvokedDispatcher;
|
||||||
|
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
|
|
||||||
@@ -13,10 +16,26 @@ public class HelpActivity extends Activity {
|
|||||||
private SpinnerDialog loadingDialog;
|
private SpinnerDialog loadingDialog;
|
||||||
private WebView webView;
|
private WebView webView;
|
||||||
|
|
||||||
|
private boolean backCallbackRegistered;
|
||||||
|
private OnBackInvokedCallback onBackInvokedCallback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
onBackInvokedCallback = new OnBackInvokedCallback() {
|
||||||
|
@Override
|
||||||
|
public void onBackInvoked() {
|
||||||
|
// We should always be able to go back because we unregister our callback
|
||||||
|
// when we can't go back. Nonetheless, we will still check anyway.
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
webView = new WebView(this);
|
webView = new WebView(this);
|
||||||
setContentView(webView);
|
setContentView(webView);
|
||||||
|
|
||||||
@@ -39,6 +58,8 @@ public class HelpActivity extends Activity {
|
|||||||
getResources().getString(R.string.help_loading_title),
|
getResources().getString(R.string.help_loading_title),
|
||||||
getResources().getString(R.string.help_loading_msg), false);
|
getResources().getString(R.string.help_loading_msg), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshBackDispatchState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,6 +68,8 @@ public class HelpActivity extends Activity {
|
|||||||
loadingDialog.dismiss();
|
loadingDialog.dismiss();
|
||||||
loadingDialog = null;
|
loadingDialog = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshBackDispatchState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,6 +82,31 @@ public class HelpActivity extends Activity {
|
|||||||
webView.loadUrl(getIntent().getData().toString());
|
webView.loadUrl(getIntent().getData().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshBackDispatchState() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (webView.canGoBack() && !backCallbackRegistered) {
|
||||||
|
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
|
||||||
|
OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
|
||||||
|
backCallbackRegistered = true;
|
||||||
|
}
|
||||||
|
else if (!webView.canGoBack() && backCallbackRegistered) {
|
||||||
|
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||||
|
backCallbackRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (backCallbackRegistered) {
|
||||||
|
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
// Back goes back through the WebView history
|
// Back goes back through the WebView history
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
UiHelper.notifyNewRootView(this);
|
UiHelper.notifyNewRootView(this);
|
||||||
|
|
||||||
|
// Allow floating expanded PiP overlays while browsing PCs
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
setShouldDockBigOverlays(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Set default preferences if we've never been run
|
// Set default preferences if we've never been run
|
||||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.media.AudioAttributes;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.CombinedVibration;
|
import android.os.CombinedVibration;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.os.VibrationAttributes;
|
||||||
import android.os.VibrationEffect;
|
import android.os.VibrationEffect;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import android.os.VibratorManager;
|
import android.os.VibratorManager;
|
||||||
@@ -24,6 +25,7 @@ import com.limelight.LimeLog;
|
|||||||
import com.limelight.binding.input.driver.AbstractController;
|
import com.limelight.binding.input.driver.AbstractController;
|
||||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||||
import com.limelight.binding.input.driver.UsbDriverService;
|
import com.limelight.binding.input.driver.UsbDriverService;
|
||||||
|
import com.limelight.binding.input.shield.ShieldControllerExtensionsHandler;
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.ControllerPacket;
|
import com.limelight.nvstream.input.ControllerPacket;
|
||||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
@@ -60,6 +62,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||||
private final GameGestures gestures;
|
private final GameGestures gestures;
|
||||||
private final Vibrator deviceVibrator;
|
private final Vibrator deviceVibrator;
|
||||||
|
private final ShieldControllerExtensionsHandler shieldControllerExtensionsHandler;
|
||||||
private boolean hasGameController;
|
private boolean hasGameController;
|
||||||
|
|
||||||
private final PreferenceConfiguration prefConfig;
|
private final PreferenceConfiguration prefConfig;
|
||||||
@@ -71,6 +74,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
this.gestures = gestures;
|
this.gestures = gestures;
|
||||||
this.prefConfig = prefConfig;
|
this.prefConfig = prefConfig;
|
||||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
this.shieldControllerExtensionsHandler = new ShieldControllerExtensionsHandler(activityContext);
|
||||||
|
|
||||||
int deadzonePercentage = prefConfig.deadzonePercentage;
|
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||||
|
|
||||||
@@ -199,6 +203,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
deviceContext.destroy();
|
deviceContext.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shieldControllerExtensionsHandler.destroy();
|
||||||
deviceVibrator.cancel();
|
deviceVibrator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +509,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
LimeLog.info(dev.toString());
|
LimeLog.info(dev.toString());
|
||||||
|
|
||||||
|
context.inputDevice = dev;
|
||||||
context.name = devName;
|
context.name = devName;
|
||||||
context.id = dev.getId();
|
context.id = dev.getId();
|
||||||
context.external = isExternal(dev);
|
context.external = isExternal(dev);
|
||||||
@@ -1357,7 +1363,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.vibrate(combo.combine());
|
VibrationAttributes.Builder vibrationAttributes = new VibrationAttributes.Builder()
|
||||||
|
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
vibrationAttributes.setUsage(VibrationAttributes.USAGE_MEDIA);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.vibrate(combo.combine(), vibrationAttributes.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||||
@@ -1382,10 +1395,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (vibrator.hasAmplitudeControl()) {
|
if (vibrator.hasAmplitudeControl()) {
|
||||||
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
||||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
.setUsage(AudioAttributes.USAGE_GAME)
|
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||||
.build();
|
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||||
vibrator.vibrate(effect, audioAttributes);
|
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
|
||||||
|
.build();
|
||||||
|
vibrator.vibrate(effect, vibrationAttributes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_GAME)
|
||||||
|
.build();
|
||||||
|
vibrator.vibrate(effect, audioAttributes);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1395,7 +1417,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
long pwmPeriod = 20;
|
long pwmPeriod = 20;
|
||||||
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
||||||
long offTime = pwmPeriod - onTime;
|
long offTime = pwmPeriod - onTime;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||||
|
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||||
|
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
|
||||||
|
.build();
|
||||||
|
vibrator.vibrate(VibrationEffect.createWaveform(new long[]{0, onTime, offTime}, 0), vibrationAttributes);
|
||||||
|
}
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||||
.setUsage(AudioAttributes.USAGE_GAME)
|
.setUsage(AudioAttributes.USAGE_GAME)
|
||||||
.build();
|
.build();
|
||||||
@@ -1416,10 +1445,34 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
if (deviceContext.controllerNumber == controllerNumber) {
|
if (deviceContext.controllerNumber == controllerNumber) {
|
||||||
foundMatchingDevice = true;
|
foundMatchingDevice = true;
|
||||||
|
|
||||||
|
// Cancel pending rumble repeat timer if one exists
|
||||||
|
if (deviceContext.rumbleRepeatTimer != null) {
|
||||||
|
deviceContext.rumbleRepeatTimer.cancel();
|
||||||
|
deviceContext.rumbleRepeatTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
||||||
vibrated = true;
|
vibrated = true;
|
||||||
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
||||||
}
|
}
|
||||||
|
// On Shield devices, we can use their special API to rumble Shield controllers
|
||||||
|
else if (shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
|
||||||
|
vibrated = true;
|
||||||
|
|
||||||
|
// The Shield controller can only rumble up to 1 second at a time, so we will call rumble again
|
||||||
|
// every 500 ms until the host PC gives us another rumble value.
|
||||||
|
if (lowFreqMotor != 0 || highFreqMotor != 0) {
|
||||||
|
deviceContext.rumbleRepeatTimer = new Timer("Rumble Repeat - "+deviceContext.name, true);
|
||||||
|
deviceContext.rumbleRepeatTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor);
|
||||||
|
}
|
||||||
|
}, 500, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If all else fails, we have to try the old Vibrator API
|
||||||
else if (deviceContext.vibrator != null) {
|
else if (deviceContext.vibrator != null) {
|
||||||
vibrated = true;
|
vibrated = true;
|
||||||
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||||
@@ -1907,6 +1960,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
public String name;
|
public String name;
|
||||||
public VibratorManager vibratorManager;
|
public VibratorManager vibratorManager;
|
||||||
public Vibrator vibrator;
|
public Vibrator vibrator;
|
||||||
|
public InputDevice inputDevice;
|
||||||
|
public Timer rumbleRepeatTimer;
|
||||||
|
|
||||||
public int leftStickXAxis = -1;
|
public int leftStickXAxis = -1;
|
||||||
public int leftStickYAxis = -1;
|
public int leftStickYAxis = -1;
|
||||||
@@ -1958,6 +2013,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
else if (vibrator != null) {
|
else if (vibrator != null) {
|
||||||
vibrator.cancel();
|
vibrator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rumbleRepeatTimer != null) {
|
||||||
|
rumbleRepeatTimer.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
package com.limelight.binding.input;
|
package com.limelight.binding.input;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to translate a Android key code into the codes GFE is expecting
|
* Class to translate a Android key code into the codes GFE is expecting
|
||||||
* @author Diego Waxemberg
|
* @author Diego Waxemberg
|
||||||
* @author Cameron Gutman
|
* @author Cameron Gutman
|
||||||
*/
|
*/
|
||||||
public class KeyboardTranslator {
|
public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GFE's prefix for every key code
|
* GFE's prefix for every key code
|
||||||
@@ -48,6 +55,55 @@ public class KeyboardTranslator {
|
|||||||
public static final int VK_QUOTE = 222;
|
public static final int VK_QUOTE = 222;
|
||||||
public static final int VK_PAUSE = 19;
|
public static final int VK_PAUSE = 19;
|
||||||
|
|
||||||
|
private static class KeyboardMapping {
|
||||||
|
private final InputDevice device;
|
||||||
|
private final int[] deviceKeyCodeToQwertyKeyCode;
|
||||||
|
|
||||||
|
@TargetApi(33)
|
||||||
|
public KeyboardMapping(InputDevice device) {
|
||||||
|
int maxKeyCode = KeyEvent.getMaxKeyCode();
|
||||||
|
|
||||||
|
this.device = device;
|
||||||
|
this.deviceKeyCodeToQwertyKeyCode = new int[maxKeyCode + 1];
|
||||||
|
|
||||||
|
// Any unmatched keycodes are treated as unknown
|
||||||
|
Arrays.fill(deviceKeyCodeToQwertyKeyCode, KeyEvent.KEYCODE_UNKNOWN);
|
||||||
|
|
||||||
|
for (int i = 0; i <= maxKeyCode; i++) {
|
||||||
|
int deviceKeyCode = device.getKeyCodeForKeyLocation(i);
|
||||||
|
if (deviceKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
|
deviceKeyCodeToQwertyKeyCode[deviceKeyCode] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(33)
|
||||||
|
public int getDeviceKeyCodeForQwertyKeyCode(int qwertyKeyCode) {
|
||||||
|
return device.getKeyCodeForKeyLocation(qwertyKeyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQwertyKeyCodeForDeviceKeyCode(int deviceKeyCode) {
|
||||||
|
if (deviceKeyCode > KeyEvent.getMaxKeyCode()) {
|
||||||
|
return KeyEvent.KEYCODE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceKeyCodeToQwertyKeyCode[deviceKeyCode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SparseArray<KeyboardMapping> keyboardMappings = new SparseArray<>();
|
||||||
|
|
||||||
|
public KeyboardTranslator() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
for (int deviceId : InputDevice.getDeviceIds()) {
|
||||||
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
|
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
keyboardMappings.set(deviceId, new KeyboardMapping(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean needsShift(int keycode) {
|
public static boolean needsShift(int keycode) {
|
||||||
switch (keycode)
|
switch (keycode)
|
||||||
{
|
{
|
||||||
@@ -65,10 +121,24 @@ public class KeyboardTranslator {
|
|||||||
/**
|
/**
|
||||||
* Translates the given keycode and returns the GFE keycode
|
* Translates the given keycode and returns the GFE keycode
|
||||||
* @param keycode the code to be translated
|
* @param keycode the code to be translated
|
||||||
|
* @param deviceId InputDevice.getId() or -1 if unknown
|
||||||
* @return a GFE keycode for the given keycode
|
* @return a GFE keycode for the given keycode
|
||||||
*/
|
*/
|
||||||
public static short translate(int keycode) {
|
public short translate(int keycode, int deviceId) {
|
||||||
int translated;
|
int translated;
|
||||||
|
|
||||||
|
// If a device ID was provided, look up the keyboard mapping
|
||||||
|
if (deviceId >= 0) {
|
||||||
|
KeyboardMapping mapping = keyboardMappings.get(deviceId);
|
||||||
|
if (mapping != null) {
|
||||||
|
// Try to map this device-specific keycode onto a QWERTY layout.
|
||||||
|
// GFE assumes incoming keycodes are from a QWERTY keyboard.
|
||||||
|
int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode);
|
||||||
|
if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
|
keycode = qwertyKeyCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is a poor man's mapping between Android key codes
|
// This is a poor man's mapping between Android key codes
|
||||||
// and Windows VK_* codes. For all defined VK_ codes, see:
|
// and Windows VK_* codes. For all defined VK_ codes, see:
|
||||||
@@ -294,4 +364,30 @@ public class KeyboardTranslator {
|
|||||||
return (short) ((KEY_PREFIX << 8) | translated);
|
return (short) ((KEY_PREFIX << 8) | translated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceAdded(int index) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
InputDevice device = InputDevice.getDevice(index);
|
||||||
|
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
keyboardMappings.put(index, new KeyboardMapping(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceRemoved(int index) {
|
||||||
|
keyboardMappings.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceChanged(int index) {
|
||||||
|
keyboardMappings.remove(index);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
InputDevice device = InputDevice.getDevice(index);
|
||||||
|
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
keyboardMappings.set(index, new KeyboardMapping(device));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package com.limelight.binding.input.capture;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import com.limelight.BuildConfig;
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.LimelightBuildProps;
|
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
@@ -16,7 +16,7 @@ public class InputCaptureManager {
|
|||||||
}
|
}
|
||||||
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
||||||
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
||||||
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
else if (!BuildConfig.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||||
return new ShieldCaptureProvider(activity);
|
return new ShieldCaptureProvider(activity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
|
|
||||||
private UsbManager usbManager;
|
private UsbManager usbManager;
|
||||||
private PreferenceConfiguration prefConfig;
|
private PreferenceConfiguration prefConfig;
|
||||||
|
private boolean started;
|
||||||
|
|
||||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||||
private final UsbDriverBinder binder = new UsbDriverBinder();
|
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||||
@@ -36,6 +37,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
||||||
|
|
||||||
private UsbDriverListener listener;
|
private UsbDriverListener listener;
|
||||||
|
private UsbDriverStateListener stateListener;
|
||||||
private int nextDeviceId;
|
private int nextDeviceId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,6 +95,11 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
else if (action.equals(ACTION_USB_PERMISSION)) {
|
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
|
||||||
|
// Permission dialog is now closed
|
||||||
|
if (stateListener != null) {
|
||||||
|
stateListener.onUsbPermissionPromptCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
// If we got this far, we've already found we're able to handle this device
|
// If we got this far, we've already found we're able to handle this device
|
||||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||||
handleUsbDeviceState(device);
|
handleUsbDeviceState(device);
|
||||||
@@ -112,6 +119,18 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStateListener(UsbDriverStateListener stateListener) {
|
||||||
|
UsbDriverService.this.stateListener = stateListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
UsbDriverService.this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
UsbDriverService.this.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUsbDeviceState(UsbDevice device) {
|
private void handleUsbDeviceState(UsbDevice device) {
|
||||||
@@ -121,20 +140,29 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
if (!usbManager.hasPermission(device)) {
|
if (!usbManager.hasPermission(device)) {
|
||||||
// Let's ask for permission
|
// Let's ask for permission
|
||||||
try {
|
try {
|
||||||
|
// Tell the state listener that we're about to display a permission dialog
|
||||||
|
if (stateListener != null) {
|
||||||
|
stateListener.onUsbPermissionPromptStarting();
|
||||||
|
}
|
||||||
|
|
||||||
|
int intentFlags = 0;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
// This function is not documented as throwing any exceptions (denying access
|
// This function is not documented as throwing any exceptions (denying access
|
||||||
// is indicated by calling the PendingIntent with a false result). However,
|
// is indicated by calling the PendingIntent with a false result). However,
|
||||||
// Samsung Knox has some policies which block this request, but rather than
|
// Samsung Knox has some policies which block this request, but rather than
|
||||||
// just returning a false result or returning 0 enumerated devices,
|
// just returning a false result or returning 0 enumerated devices,
|
||||||
// they throw an undocumented SecurityException from this call, crashing
|
// they throw an undocumented SecurityException from this call, crashing
|
||||||
// the whole app. :(
|
// the whole app. :(
|
||||||
int intentFlags = 0;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
|
|
||||||
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
|
||||||
}
|
|
||||||
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags));
|
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags));
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
||||||
|
if (stateListener != null) {
|
||||||
|
stateListener.onUsbPermissionPromptCompleted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -225,16 +253,23 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void start() {
|
||||||
public void onCreate() {
|
if (started) {
|
||||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
return;
|
||||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
}
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
|
||||||
// Register for USB attach broadcasts and permission completions
|
// Register for USB attach broadcasts and permission completions
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||||
filter.addAction(ACTION_USB_PERMISSION);
|
filter.addAction(ACTION_USB_PERMISSION);
|
||||||
registerReceiver(receiver, filter);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
registerReceiver(receiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
// Enumerate existing devices
|
// Enumerate existing devices
|
||||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||||
@@ -245,14 +280,16 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void stop() {
|
||||||
public void onDestroy() {
|
if (!started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
started = false;
|
||||||
|
|
||||||
// Stop the attachment receiver
|
// Stop the attachment receiver
|
||||||
unregisterReceiver(receiver);
|
unregisterReceiver(receiver);
|
||||||
|
|
||||||
// Remove listeners
|
|
||||||
listener = null;
|
|
||||||
|
|
||||||
// Stop all controllers
|
// Stop all controllers
|
||||||
while (controllers.size() > 0) {
|
while (controllers.size() > 0) {
|
||||||
// Stop and remove the controller
|
// Stop and remove the controller
|
||||||
@@ -260,8 +297,28 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
|
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
stop();
|
||||||
|
|
||||||
|
// Remove listeners
|
||||||
|
listener = null;
|
||||||
|
stateListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface UsbDriverStateListener {
|
||||||
|
void onUsbPermissionPromptStarting();
|
||||||
|
void onUsbPermissionPromptCompleted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package com.limelight.binding.input.evdev;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
import com.limelight.LimelightBuildProps;
|
import com.limelight.BuildConfig;
|
||||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||||
|
|
||||||
public class EvdevCaptureProviderShim {
|
public class EvdevCaptureProviderShim {
|
||||||
public static boolean isCaptureProviderSupported() {
|
public static boolean isCaptureProviderSupported() {
|
||||||
return LimelightBuildProps.ROOT_BUILD;
|
return BuildConfig.ROOT_BUILD;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to construct our capture provider using reflection because it isn't included in non-root builds
|
// We need to construct our capture provider using reflection because it isn't included in non-root builds
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package com.limelight.binding.input.shield;
|
||||||
|
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.IInterface;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
public interface IExposedControllerManagerListener extends IInterface {
|
||||||
|
void onDeviceAdded(String controllerToken);
|
||||||
|
void onDeviceChanged(String controllerToken, int i);
|
||||||
|
void onDeviceRemoved(String controllerToken);
|
||||||
|
|
||||||
|
public static abstract class Stub extends Binder implements IExposedControllerManagerListener {
|
||||||
|
public Stub() {
|
||||||
|
attachInterface(this, "com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder asBinder() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onTransact(int code, Parcel input, Parcel output, int flags) throws RemoteException {
|
||||||
|
switch (code) {
|
||||||
|
case 1:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
onDeviceAdded(input.readString());
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
onDeviceChanged(input.readString(), input.readInt());
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
onDeviceRemoved(input.readString());
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||||
|
// Don't care
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onTransact(code, input, output, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+291
@@ -0,0 +1,291 @@
|
|||||||
|
package com.limelight.binding.input.shield;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
|
||||||
|
import com.limelight.LimeLog;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class ShieldControllerExtensionsHandler implements InputManager.InputDeviceListener {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private IBinder binder;
|
||||||
|
private ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
|
binder = iBinder;
|
||||||
|
|
||||||
|
try {
|
||||||
|
listenerId = registerListener();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
listenerId = 0;
|
||||||
|
tokenToDeviceIdMap.clear();
|
||||||
|
deviceIdToTokenMap.clear();
|
||||||
|
|
||||||
|
binder = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ConcurrentHashMap handles synchronization between the Binder thread adding/removing
|
||||||
|
// entries and callers on arbitrary threads that are doing device lookups.
|
||||||
|
//
|
||||||
|
// Since these are separate maps, they can be temporarily inconsistent (only one-way
|
||||||
|
// of the two-way mapping present). This is fine for our purposes here.
|
||||||
|
private ConcurrentHashMap<String, Integer> tokenToDeviceIdMap = new ConcurrentHashMap<>();
|
||||||
|
private ConcurrentHashMap<Integer, String> deviceIdToTokenMap = new ConcurrentHashMap<>();
|
||||||
|
private AtomicBoolean needsRefresh = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private int listenerId;
|
||||||
|
private IExposedControllerManagerListener.Stub controllerListener = new IExposedControllerManagerListener.Stub() {
|
||||||
|
@Override
|
||||||
|
public void onDeviceAdded(String controllerToken) {
|
||||||
|
try {
|
||||||
|
int inputDeviceId = getInputDeviceId(controllerToken);
|
||||||
|
|
||||||
|
LimeLog.info("Shield controller added: " + controllerToken + " -> " + inputDeviceId);
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.put(controllerToken, inputDeviceId);
|
||||||
|
deviceIdToTokenMap.put(inputDeviceId, controllerToken);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeviceChanged(String controllerToken, int i) {
|
||||||
|
LimeLog.info("Shield controller changed: " + controllerToken + " " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeviceRemoved(String controllerToken) {
|
||||||
|
LimeLog.info("Shield controller removed: " + controllerToken);
|
||||||
|
|
||||||
|
Integer deviceId = tokenToDeviceIdMap.remove(controllerToken);
|
||||||
|
if (deviceId != null) {
|
||||||
|
deviceIdToTokenMap.remove(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public ShieldControllerExtensionsHandler(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||||
|
inputManager.registerInputDeviceListener(this, null);
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setClassName("com.nvidia.blakepairing", "com.nvidia.blakepairing.AccessoryService");
|
||||||
|
if (!context.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) {
|
||||||
|
LimeLog.info("com.nvidia.blakepairing.AccessoryService is not available on this device");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getControllerToken(InputDevice device) {
|
||||||
|
// Refresh device ID <-> token mappings if one of our devices was removed
|
||||||
|
if (needsRefresh.compareAndSet(true, false)) {
|
||||||
|
try {
|
||||||
|
LimeLog.info("Refreshing controller token mappings");
|
||||||
|
|
||||||
|
// We have to enumerate tokenToDeviceIdMap rather than deviceIdToTokenMap
|
||||||
|
// because we remove the deviceIdToTokenMap entry when the device goes away.
|
||||||
|
HashMap<String, Integer> newTokenToDeviceIdMap = new HashMap<>();
|
||||||
|
HashMap<Integer, String> newDeviceIdToTokenMap = new HashMap<>();
|
||||||
|
for (String existingToken : tokenToDeviceIdMap.keySet()) {
|
||||||
|
int deviceId = getInputDeviceId(existingToken);
|
||||||
|
if (deviceId != 0) {
|
||||||
|
newTokenToDeviceIdMap.put(existingToken, deviceId);
|
||||||
|
newDeviceIdToTokenMap.put(deviceId, existingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.clear();
|
||||||
|
deviceIdToTokenMap.clear();
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.putAll(newTokenToDeviceIdMap);
|
||||||
|
deviceIdToTokenMap.putAll(newDeviceIdToTokenMap);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceIdToTokenMap.get(device.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean rumble(InputDevice device, int lowFreqMotor, int highFreqMotor) {
|
||||||
|
String controllerToken = getControllerToken(device);
|
||||||
|
if (controllerToken != null) {
|
||||||
|
try {
|
||||||
|
return rumble(controllerToken, lowFreqMotor, highFreqMotor);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||||
|
inputManager.unregisterInputDeviceListener(this);
|
||||||
|
|
||||||
|
tokenToDeviceIdMap.clear();
|
||||||
|
deviceIdToTokenMap.clear();
|
||||||
|
|
||||||
|
if (listenerId != 0) {
|
||||||
|
try {
|
||||||
|
unregisterListener(listenerId);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
listenerId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binder != null) {
|
||||||
|
context.unbindService(serviceConnection);
|
||||||
|
binder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int registerListener() throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeStrongBinder(controllerListener);
|
||||||
|
|
||||||
|
binder.transact(20, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt();
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unregisterListener(int listenerId) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeInt(listenerId);
|
||||||
|
|
||||||
|
binder.transact(21, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt() != 0;
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInputDeviceId(String controllerToken) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeString(controllerToken);
|
||||||
|
|
||||||
|
binder.transact(13, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt();
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rumble duration maximum of 1 second
|
||||||
|
private boolean rumble(String controllerToken, int lowFreqMotor, int highFreqMotor) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeString(controllerToken);
|
||||||
|
input.writeInt(lowFreqMotor);
|
||||||
|
input.writeInt(highFreqMotor);
|
||||||
|
|
||||||
|
binder.transact(18, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt() != 0;
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rumble duration maximum of 1.5 seconds
|
||||||
|
private boolean rumbleWithDuration(String controllerToken, int lowFreqMotor, int highFreqMotor, long durationMs) throws RemoteException {
|
||||||
|
if (binder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel input = Parcel.obtain();
|
||||||
|
Parcel output = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||||
|
input.writeString(controllerToken);
|
||||||
|
input.writeInt(lowFreqMotor);
|
||||||
|
input.writeInt(highFreqMotor);
|
||||||
|
input.writeLong(durationMs);
|
||||||
|
|
||||||
|
binder.transact(19, input, output, 0);
|
||||||
|
|
||||||
|
output.readException();
|
||||||
|
return output.readInt() != 0;
|
||||||
|
} finally {
|
||||||
|
input.recycle();
|
||||||
|
output.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceAdded(int deviceId) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceChanged(int deviceId) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceRemoved(int deviceId) {
|
||||||
|
// Remove the device ID to token mapping, but leave the token mapping to device ID
|
||||||
|
// mapping so we will re-enumerate it when we next try to rumble a controller.
|
||||||
|
if (deviceIdToTokenMap.remove(deviceId) != null) {
|
||||||
|
needsRefresh.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.limelight.binding.video;
|
package com.limelight.binding.video;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
@@ -22,7 +23,8 @@ 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.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
import android.view.Choreographer;
|
import android.view.Choreographer;
|
||||||
@@ -87,6 +89,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
|
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
|
||||||
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
|
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
|
||||||
private long lastRenderedFrameTimeNanos;
|
private long lastRenderedFrameTimeNanos;
|
||||||
|
private HandlerThread choreographerHandlerThread;
|
||||||
|
private Handler choreographerHandler;
|
||||||
|
|
||||||
private int numSpsIn;
|
private int numSpsIn;
|
||||||
private int numPpsIn;
|
private int numPpsIn;
|
||||||
@@ -102,6 +106,61 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps);
|
||||||
|
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
|
||||||
|
if (perfPoints != null) {
|
||||||
|
for (MediaCodecInfo.VideoCapabilities.PerformancePoint perfPoint : perfPoints) {
|
||||||
|
// If we find a performance point that covers our target, we're good to go
|
||||||
|
if (perfPoint.covers(targetPerfPoint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We had performance point data but none met the specified streaming settings
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-through to try the Android M API if there's no performance point data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
try {
|
||||||
|
// We'll ask the decoder what it can do for us at this resolution and see if our
|
||||||
|
// requested frame rate falls below or inside the range of achievable frame rates.
|
||||||
|
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height);
|
||||||
|
return prefs.fps <= fpsRange.getUpper();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Video size not supported at any frame rate
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
MediaCodecInfo.VideoCapabilities avcCaps = avcDecoderInfo.getCapabilitiesForType("video/avc").getVideoCapabilities();
|
||||||
|
MediaCodecInfo.VideoCapabilities hevcCaps = hevcDecoderInfo.getCapabilitiesForType("video/hevc").getVideoCapabilities();
|
||||||
|
|
||||||
|
Boolean avcCanMeetPP = decoderCanMeetPerformancePoint(avcCaps, prefs);
|
||||||
|
Boolean hevcCanMeetPP = decoderCanMeetPerformancePoint(hevcCaps, prefs);
|
||||||
|
|
||||||
|
// If one or both codecs lack performance data, don't do anything
|
||||||
|
if (avcCanMeetPP == null || hevcCanMeetPP == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !avcCanMeetPP && hevcCanMeetPP;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No performance data
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
||||||
// Don't return anything if HEVC is forced off
|
// Don't return anything if HEVC is forced off
|
||||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
||||||
@@ -113,16 +172,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
|
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
|
||||||
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
|
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
|
||||||
// for even required levels of HEVC.
|
// for even required levels of HEVC.
|
||||||
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
MediaCodecInfo hevcDecoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||||
if (decoderInfo != null) {
|
if (hevcDecoderInfo != null) {
|
||||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, prefs)) {
|
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(hevcDecoderInfo.getName(), meteredNetwork, prefs)) {
|
||||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+hevcDecoderInfo.getName());
|
||||||
|
|
||||||
|
// Force HEVC enabled if the user asked for it
|
||||||
|
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
|
||||||
|
LimeLog.info("Forcing HEVC enabled despite non-whitelisted decoder");
|
||||||
|
}
|
||||||
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
|
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
|
||||||
|
else if (requestedHdr) {
|
||||||
|
LimeLog.info("Forcing HEVC enabled for HDR streaming");
|
||||||
|
}
|
||||||
// > 4K streaming also requires HEVC, so force it on there too.
|
// > 4K streaming also requires HEVC, so force it on there too.
|
||||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr ||
|
else if (prefs.width > 4096 || prefs.height > 4096) {
|
||||||
prefs.width > 4096 || prefs.height > 4096) {
|
LimeLog.info("Forcing HEVC enabled for over 4K streaming");
|
||||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
}
|
||||||
|
// Use HEVC if the H.264 decoder is unable to meet the performance point
|
||||||
|
else if (avcDecoder != null && decoderCanMeetPerformancePointWithHevcAndNotAvc(avcDecoder, hevcDecoderInfo, prefs)) {
|
||||||
|
LimeLog.info("Using non-whitelisted HEVC decoder to meet performance point");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return null;
|
||||||
@@ -130,7 +199,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoderInfo;
|
return hevcDecoderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRenderTarget(SurfaceHolder renderTarget) {
|
public void setRenderTarget(SurfaceHolder renderTarget) {
|
||||||
@@ -232,6 +301,77 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
return this.videoFormat;
|
return this.videoFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MediaFormat createBaseMediaFormat(String mimeType) {
|
||||||
|
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, initialWidth, initialHeight);
|
||||||
|
|
||||||
|
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, refreshRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
|
||||||
|
// so we don't fill these pre-KitKat
|
||||||
|
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, initialWidth);
|
||||||
|
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, initialHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
return videoFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryConfigureDecoder(MediaCodecInfo selectedDecoderInfo, MediaFormat format) {
|
||||||
|
try {
|
||||||
|
videoDecoder = MediaCodec.createByCodecName(selectedDecoderInfo.getName());
|
||||||
|
LimeLog.info("Configuring with format: "+format);
|
||||||
|
|
||||||
|
videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
|
||||||
|
|
||||||
|
configuredFormat = format;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// This will contain the actual accepted input format attributes
|
||||||
|
inputFormat = videoDecoder.getInputFormat();
|
||||||
|
LimeLog.info("Input format: "+inputFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||||
|
|
||||||
|
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
|
||||||
|
@Override
|
||||||
|
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
|
||||||
|
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
|
||||||
|
if (delta >= 0 && delta < 1000) {
|
||||||
|
if (USE_FRAME_RENDER_TIME) {
|
||||||
|
activeWindowVideoStats.totalTimeMs += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Using codec "+selectedDecoderInfo.getName()+" for hardware decoding "+format.getString(MediaFormat.KEY_MIME));
|
||||||
|
|
||||||
|
// Start the decoder
|
||||||
|
videoDecoder.start();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
legacyInputBuffers = videoDecoder.getInputBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
if (videoDecoder != null) {
|
||||||
|
videoDecoder.release();
|
||||||
|
videoDecoder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int setup(int format, int width, int height, int redrawRate) {
|
public int setup(int format, int width, int height, int redrawRate) {
|
||||||
this.initialWidth = width;
|
this.initialWidth = width;
|
||||||
@@ -296,71 +436,23 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderInfo, mimeType);
|
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderInfo, mimeType);
|
||||||
fusedIdrFrame = MediaCodecHelper.decoderSupportsFusedIdrFrame(selectedDecoderInfo, mimeType);
|
fusedIdrFrame = MediaCodecHelper.decoderSupportsFusedIdrFrame(selectedDecoderInfo, mimeType);
|
||||||
|
|
||||||
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
for (int tryNumber = 0;; tryNumber++) {
|
||||||
// due to implementation problems
|
LimeLog.info("Decoder configuration try: "+tryNumber);
|
||||||
try {
|
|
||||||
videoDecoder = MediaCodec.createByCodecName(selectedDecoderInfo.getName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
|
MediaFormat mediaFormat = createBaseMediaFormat(mimeType);
|
||||||
|
|
||||||
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
|
// This will try low latency options until we find one that works (or we give up).
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
boolean newFormat = MediaCodecHelper.setDecoderLowLatencyOptions(mediaFormat, selectedDecoderInfo, tryNumber);
|
||||||
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, redrawRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
|
if (tryConfigureDecoder(selectedDecoderInfo, mediaFormat)) {
|
||||||
// so we don't fill these pre-KitKat
|
// Success!
|
||||||
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
break;
|
||||||
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
|
|
||||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaCodecHelper.setDecoderLowLatencyOptions(videoFormat, selectedDecoderInfo, mimeType);
|
|
||||||
|
|
||||||
configuredFormat = videoFormat;
|
|
||||||
LimeLog.info("Configuring with format: "+configuredFormat);
|
|
||||||
|
|
||||||
try {
|
|
||||||
videoDecoder.configure(videoFormat, renderTarget.getSurface(), null, 0);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
// This will contain the actual accepted input format attributes
|
|
||||||
inputFormat = videoDecoder.getInputFormat();
|
|
||||||
LimeLog.info("Input format: "+inputFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
if (!newFormat) {
|
||||||
|
// We couldn't even configure a decoder without any low latency options
|
||||||
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
return -5;
|
||||||
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
|
|
||||||
@Override
|
|
||||||
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
|
|
||||||
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
|
|
||||||
if (delta >= 0 && delta < 1000) {
|
|
||||||
if (USE_FRAME_RENDER_TIME) {
|
|
||||||
activeWindowVideoStats.totalTimeMs += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LimeLog.info("Using codec "+selectedDecoderInfo.getName()+" for hardware decoding "+mimeType);
|
|
||||||
|
|
||||||
// Start the decoder
|
|
||||||
videoDecoder.start();
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
legacyInputBuffers = videoDecoder.getInputBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return -5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -454,6 +546,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
Choreographer.getInstance().postFrameCallback(this);
|
Choreographer.getInstance().postFrameCallback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startChoreographerThread() {
|
||||||
|
if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
||||||
|
// Not using Choreographer in this pacing mode
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a separate thread to avoid any main thread delays from delaying rendering
|
||||||
|
choreographerHandlerThread = new HandlerThread("Video - Choreographer", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_MORE_FAVORABLE);
|
||||||
|
choreographerHandlerThread.start();
|
||||||
|
|
||||||
|
// Start the frame callbacks
|
||||||
|
choreographerHandler = new Handler(choreographerHandlerThread.getLooper());
|
||||||
|
choreographerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void startRendererThread()
|
private void startRendererThread()
|
||||||
{
|
{
|
||||||
rendererThread = new Thread() {
|
rendererThread = new Thread() {
|
||||||
@@ -596,18 +708,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
startRendererThread();
|
startRendererThread();
|
||||||
|
startChoreographerThread();
|
||||||
// Start Choreographer callbacks for rendering with frame pacing in balanced mode
|
|
||||||
// NB: This must be done on a thread with a looper!
|
|
||||||
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
|
||||||
Handler h = new Handler(Looper.getMainLooper());
|
|
||||||
h.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// !!! May be called even if setup()/start() fails !!!
|
// !!! May be called even if setup()/start() fails !!!
|
||||||
@@ -620,12 +721,15 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
rendererThread.interrupt();
|
rendererThread.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Halt further Choreographer callbacks
|
// Post a quit message to the Choreographer looper (if we have one)
|
||||||
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
if (choreographerHandler != null) {
|
||||||
Handler h = new Handler(Looper.getMainLooper());
|
choreographerHandler.post(new Runnable() {
|
||||||
h.post(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// Don't allow any further messages to be queued
|
||||||
|
choreographerHandlerThread.quit();
|
||||||
|
|
||||||
|
// Deregister the frame callback (if registered)
|
||||||
Choreographer.getInstance().removeFrameCallback(MediaCodecDecoderRenderer.this);
|
Choreographer.getInstance().removeFrameCallback(MediaCodecDecoderRenderer.this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -637,6 +741,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
// May be called already, but we'll call it now to be safe
|
// May be called already, but we'll call it now to be safe
|
||||||
prepareForStop();
|
prepareForStop();
|
||||||
|
|
||||||
|
// Wait for the Choreographer looper to shut down (if we have one)
|
||||||
|
if (choreographerHandlerThread != null) {
|
||||||
|
try {
|
||||||
|
choreographerHandlerThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||||
|
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||||
|
// status back to true.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the renderer thread to shut down
|
// Wait for the renderer thread to shut down
|
||||||
try {
|
try {
|
||||||
rendererThread.join();
|
rendererThread.join();
|
||||||
@@ -1209,6 +1327,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
str += "SOC: "+Build.SOC_MANUFACTURER+" - "+Build.SOC_MODEL+"\n";
|
str += "SOC: "+Build.SOC_MANUFACTURER+" - "+Build.SOC_MODEL+"\n";
|
||||||
str += "Performance class: "+Build.VERSION.MEDIA_PERFORMANCE_CLASS+"\n";
|
str += "Performance class: "+Build.VERSION.MEDIA_PERFORMANCE_CLASS+"\n";
|
||||||
|
str += "Vendor params: ";
|
||||||
|
List<String> params = renderer.videoDecoder.getSupportedVendorParameters();
|
||||||
|
if (params.isEmpty()) {
|
||||||
|
str += "NONE";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (String param : params) {
|
||||||
|
str += param + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str += "\n";
|
||||||
}
|
}
|
||||||
str += "Foreground: "+renderer.foreground+"\n";
|
str += "Foreground: "+renderer.foreground+"\n";
|
||||||
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class MediaCodecHelper {
|
|||||||
private static final List<String> qualcommDecoderPrefixes;
|
private static final List<String> qualcommDecoderPrefixes;
|
||||||
private static final List<String> kirinDecoderPrefixes;
|
private static final List<String> kirinDecoderPrefixes;
|
||||||
private static final List<String> exynosDecoderPrefixes;
|
private static final List<String> exynosDecoderPrefixes;
|
||||||
|
private static final List<String> amlogicDecoderPrefixes;
|
||||||
|
|
||||||
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
||||||
|
|
||||||
@@ -135,24 +136,22 @@ public class MediaCodecHelper {
|
|||||||
// Exynos seems to be the only HEVC decoder that works reliably
|
// Exynos seems to be the only HEVC decoder that works reliably
|
||||||
whitelistedHevcDecoders.add("omx.exynos");
|
whitelistedHevcDecoders.add("omx.exynos");
|
||||||
|
|
||||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
|
// On Darcy (Shield 2017), HEVC runs fine with no fixups required. For some reason,
|
||||||
// For some reason, other X1 implementations require bitstream fixups.
|
// other X1 implementations require bitstream fixups. However, since numReferenceFrames
|
||||||
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
|
// has been supported in GFE since late 2017, we'll go ahead and enable HEVC for all
|
||||||
|
// device models.
|
||||||
|
//
|
||||||
|
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||||
|
// whether the performance is good enough to use for streaming, but they're
|
||||||
|
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||||
|
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
||||||
|
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
||||||
|
// check for it here. Since there are 2 models of Shield Tablet (possibly
|
||||||
|
// more with LTE), I will also exclude pre-Oreo OSes since only Shield ATV
|
||||||
|
// got an Oreo update.
|
||||||
|
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
whitelistedHevcDecoders.add("omx.nvidia");
|
whitelistedHevcDecoders.add("omx.nvidia");
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
|
||||||
}
|
|
||||||
|
|
||||||
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
|
||||||
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
|
|
||||||
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
|
||||||
whitelistedHevcDecoders.add("omx.mtk");
|
|
||||||
|
|
||||||
// This broke at some point on the Fire TV 3 and now the decoder
|
|
||||||
// never produces any output frames.
|
|
||||||
//whitelistedHevcDecoders.add("omx.amlogic");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
|
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
|
||||||
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
|
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
|
||||||
@@ -162,8 +161,14 @@ public class MediaCodecHelper {
|
|||||||
|
|
||||||
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
|
// 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
|
// 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).
|
// running Android 9 or later.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
//
|
||||||
|
// NB: We don't do this on Sabrina (GCWGTV) because H.264 is lower latency when we use
|
||||||
|
// vendor.low-latency.enable. We will still use HEVC if decoderCanMeetPerformancePointWithHevcAndNotAvc()
|
||||||
|
// determines it's the only way to meet the performance requirements.
|
||||||
|
//
|
||||||
|
// FIXME: Should we do this for all Amlogic S905X SoCs?
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Build.DEVICE.equalsIgnoreCase("sabrina")) {
|
||||||
whitelistedHevcDecoders.add("omx.amlogic");
|
whitelistedHevcDecoders.add("omx.amlogic");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +216,12 @@ public class MediaCodecHelper {
|
|||||||
exynosDecoderPrefixes.add("omx.exynos");
|
exynosDecoderPrefixes.add("omx.exynos");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
amlogicDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
|
amlogicDecoderPrefixes.add("omx.amlogic");
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isPowerVR(String glRenderer) {
|
private static boolean isPowerVR(String glRenderer) {
|
||||||
return glRenderer.toLowerCase().contains("powervr");
|
return glRenderer.toLowerCase().contains("powervr");
|
||||||
}
|
}
|
||||||
@@ -269,6 +280,19 @@ public class MediaCodecHelper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||||
|
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
|
||||||
|
// We still have to check Build.MANUFACTURER to catch Amazon Fire tablets.
|
||||||
|
if (context.getPackageManager().hasSystemFeature("amazon.hardware.fire_tv") ||
|
||||||
|
Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||||
|
whitelistedHevcDecoders.add("omx.mtk");
|
||||||
|
|
||||||
|
// This requires setting vdec-lowlatency on the Fire TV 3, otherwise the decoder
|
||||||
|
// never produces any output frames. See comment above for details on why we only
|
||||||
|
// do this for Fire TV devices.
|
||||||
|
whitelistedHevcDecoders.add("omx.amlogic");
|
||||||
|
}
|
||||||
|
|
||||||
ActivityManager activityManager =
|
ActivityManager activityManager =
|
||||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
||||||
@@ -388,41 +412,94 @@ public class MediaCodecHelper {
|
|||||||
!isAdreno620;
|
!isAdreno620;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, String mimeType) {
|
public static boolean setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, int tryNumber) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && decoderSupportsAndroidRLowLatency(decoderInfo, mimeType)) {
|
// Options here should be tried in the order of most to least risky. The decoder will use
|
||||||
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
|
// the first MediaFormat that doesn't fail in configure().
|
||||||
|
|
||||||
|
boolean setNewOption = false;
|
||||||
|
|
||||||
|
if (tryNumber < 1) {
|
||||||
|
// Official Android 11+ low latency option (KEY_LOW_LATENCY).
|
||||||
|
videoFormat.setInteger("low-latency", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
|
||||||
|
// If this decoder officially supports FEATURE_LowLatency, we will just use that alone
|
||||||
|
// for try 0. Otherwise, we'll include it as best effort with other options.
|
||||||
|
if (decoderSupportsAndroidRLowLatency(decoderInfo, videoFormat.getString(MediaFormat.KEY_MIME))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
if (tryNumber < 2) {
|
||||||
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
// MediaTek decoders don't use vendor-defined keys for low latency mode. Instead, they have a modified
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
// version of AOSP's ACodec.cpp which supports the "vdec-lowlatency" option. This option is passed down
|
||||||
|
// to the decoder as OMX.MTK.index.param.video.LowLatencyDecode.
|
||||||
//
|
//
|
||||||
// MediaCodec vendor extension support was introduced in Android 8.0:
|
// This option is also plumbed for Amazon Amlogic-based devices like the Fire TV 3. Not only does it
|
||||||
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
// reduce latency on Amlogic, it fixes the HEVC bug that causes the decoder to not output any frames.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// On Fire TV 3, vdec-lowlatency is translated to OMX.amazon.fireos.index.video.lowLatencyDecode.
|
||||||
// Try vendor-specific low latency options
|
//
|
||||||
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
|
// https://github.com/yuan1617/Framwork/blob/master/frameworks/av/media/libstagefright/ACodec.cpp
|
||||||
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
// https://github.com/iykex/vendor_mediatek_proprietary_hardware/blob/master/libomx/video/MtkOmxVdecEx/MtkOmxVdecEx.h
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
videoFormat.setInteger("vdec-lowlatency", 1);
|
||||||
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
setNewOption = true;
|
||||||
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
}
|
||||||
|
|
||||||
|
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
||||||
|
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
||||||
|
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
||||||
|
//
|
||||||
|
// MediaCodec vendor extension support was introduced in Android 8.0:
|
||||||
|
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// Try vendor-specific low latency options
|
||||||
|
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
||||||
|
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
||||||
|
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
||||||
|
//
|
||||||
|
// We will first try both, then try vendor.qti-ext-dec-low-latency.enable alone if that fails
|
||||||
|
if (tryNumber < 3) {
|
||||||
|
videoFormat.setInteger("vendor.qti-ext-dec-picture-order.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
}
|
}
|
||||||
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
|
if (tryNumber < 4) {
|
||||||
|
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
if (tryNumber < 3) {
|
||||||
// Kirin low latency options
|
// Kirin low latency options
|
||||||
// https://developer.huawei.com/consumer/cn/forum/topic/0202325564295980115
|
// https://developer.huawei.com/consumer/cn/forum/topic/0202325564295980115
|
||||||
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1);
|
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1);
|
||||||
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy", -1);
|
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy", -1);
|
||||||
|
setNewOption = true;
|
||||||
}
|
}
|
||||||
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
|
}
|
||||||
|
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
|
||||||
|
if (tryNumber < 3) {
|
||||||
// Exynos low latency option for H.264 decoder
|
// Exynos low latency option for H.264 decoder
|
||||||
videoFormat.setInteger("vendor.rtc-ext-dec-low-latency.enable", 1);
|
videoFormat.setInteger("vendor.rtc-ext-dec-low-latency.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (isDecoderInList(amlogicDecoderPrefixes, decoderInfo.getName())) {
|
||||||
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
|
if (tryNumber < 3) {
|
||||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
// Amlogic low latency vendor extension
|
||||||
|
// https://github.com/codewalkerster/android_vendor_amlogic_common_prebuilt_libstagefrighthw/commit/41fefc4e035c476d58491324a5fe7666bfc2989e
|
||||||
|
videoFormat.setInteger("vendor.low-latency.enable", 1);
|
||||||
|
setNewOption = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: We should probably integrate this into the try system
|
||||||
|
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
|
||||||
|
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return setNewOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderSupportsFusedIdrFrame(MediaCodecInfo decoderInfo, String mimeType) {
|
public static boolean decoderSupportsFusedIdrFrame(MediaCodecInfo decoderInfo, String mimeType) {
|
||||||
@@ -505,20 +582,6 @@ public class MediaCodecHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, PreferenceConfiguration prefs) {
|
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, PreferenceConfiguration prefs) {
|
||||||
// TODO: Shield Tablet K1/LTE?
|
|
||||||
//
|
|
||||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
|
||||||
// whether the performance is good enough to use for streaming, but they're
|
|
||||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
|
||||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
|
||||||
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
|
||||||
// check for it here.
|
|
||||||
//
|
|
||||||
// TODO: Temporarily disabled with NVIDIA HEVC support
|
|
||||||
/*if (Build.DEVICE.equalsIgnoreCase("shieldtablet")) {
|
|
||||||
return false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
|
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
|
||||||
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
|
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
|
||||||
// so I'm restricting HEVC usage to Lollipop and higher.
|
// so I'm restricting HEVC usage to Lollipop and higher.
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class NvApp {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder str = new StringBuilder();
|
StringBuilder str = new StringBuilder();
|
||||||
str.append("Name: ").append(appName).append("\n");
|
str.append("Name: ").append(appName).append("\n");
|
||||||
str.append("HDR: ").append(hdrSupported ? "Yes" : "No").append("\n");
|
str.append("HDR Supported: ").append(hdrSupported ? "Yes" : "Unknown").append("\n");
|
||||||
str.append("ID: ").append(appId).append("\n");
|
str.append("ID: ").append(appId).append("\n");
|
||||||
return str.toString();
|
return str.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,21 +421,18 @@ public class NvHTTP {
|
|||||||
|
|
||||||
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
||||||
try {
|
try {
|
||||||
if (verbose) {
|
|
||||||
LimeLog.info("Requesting URL: "+getCompleteUrl(baseUrl, path, query));
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
|
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
|
||||||
String respString = resp.string();
|
String respString = resp.string();
|
||||||
resp.close();
|
resp.close();
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose && !path.equals("serverinfo")) {
|
||||||
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
|
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
|
||||||
}
|
}
|
||||||
|
|
||||||
return respString;
|
return respString;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (verbose) {
|
if (verbose && !path.equals("serverinfo")) {
|
||||||
|
LimeLog.warning(getCompleteUrl(baseUrl, path, query)+" -> "+e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -319,6 +319,13 @@ public class PreferenceConfiguration {
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
|
||||||
|
// This particular Shield TV firmware crashes when using HDR
|
||||||
|
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
|
||||||
|
return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA") &&
|
||||||
|
Build.FINGERPRINT.contains("PPR1.180610.011/4079208_2235.1395");
|
||||||
|
}
|
||||||
|
|
||||||
public static PreferenceConfiguration readPreferences(Context context) {
|
public static PreferenceConfiguration readPreferences(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||||
@@ -445,7 +452,7 @@ public class PreferenceConfiguration {
|
|||||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||||
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||||
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
||||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
|
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR) && !isShieldAtvFirmwareWithBrokenHdr();
|
||||||
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
|
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
|
||||||
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
||||||
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
|
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.os.Bundle;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
||||||
@@ -545,6 +546,15 @@ public class StreamSettings extends Activity {
|
|||||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||||
category.removePreference(findPreference("checkbox_enable_hdr"));
|
category.removePreference(findPreference("checkbox_enable_hdr"));
|
||||||
}
|
}
|
||||||
|
else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) {
|
||||||
|
LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware");
|
||||||
|
PreferenceCategory category =
|
||||||
|
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||||
|
CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr");
|
||||||
|
hdrPref.setEnabled(false);
|
||||||
|
hdrPref.setChecked(false);
|
||||||
|
hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a listener to the FPS and resolution preference
|
// Add a listener to the FPS and resolution preference
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.limelight.utils;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.app.GameManager;
|
||||||
|
import android.app.GameState;
|
||||||
import android.app.UiModeManager;
|
import android.app.UiModeManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -13,6 +15,7 @@ import android.view.View;
|
|||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import com.limelight.Game;
|
||||||
import com.limelight.R;
|
import com.limelight.R;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.preferences.PreferenceConfiguration;
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
@@ -24,6 +27,39 @@ public class UiHelper {
|
|||||||
private static final int TV_VERTICAL_PADDING_DP = 15;
|
private static final int TV_VERTICAL_PADDING_DP = 15;
|
||||||
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
||||||
|
|
||||||
|
private static void setGameModeStatus(Context context, boolean streaming, boolean loading, boolean interruptible) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
GameManager gameManager = context.getSystemService(GameManager.class);
|
||||||
|
|
||||||
|
if (streaming) {
|
||||||
|
gameManager.setGameState(new GameState(loading, interruptible ? GameState.MODE_GAMEPLAY_INTERRUPTIBLE : GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gameManager.setGameState(new GameState(loading, GameState.MODE_NONE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamConnecting(Context context) {
|
||||||
|
setGameModeStatus(context, true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamConnected(Context context) {
|
||||||
|
setGameModeStatus(context, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamEnteringPiP(Context context) {
|
||||||
|
setGameModeStatus(context, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamExitingPiP(Context context) {
|
||||||
|
setGameModeStatus(context, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyStreamEnded(Context context) {
|
||||||
|
setGameModeStatus(context, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setLocale(Activity activity)
|
public static void setLocale(Activity activity)
|
||||||
{
|
{
|
||||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||||
@@ -68,6 +104,9 @@ public class UiHelper {
|
|||||||
View rootView = activity.findViewById(android.R.id.content);
|
View rootView = activity.findViewById(android.R.id.content);
|
||||||
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
||||||
|
|
||||||
|
// Set GameState.MODE_NONE initially for all activities
|
||||||
|
setGameModeStatus(activity, false, false, false);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
// Allow this non-streaming activity to layout under notches.
|
// Allow this non-streaming activity to layout under notches.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
android:text="@string/title_add_pc"
|
android:text="@string/title_add_pc"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:preferKeepClear="true"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:layout_alignParentTop="true"/>
|
android:layout_alignParentTop="true"/>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
|
android:preferKeepClear="true"
|
||||||
android:inputType="textNoSuggestions"
|
android:inputType="textNoSuggestions"
|
||||||
android:hint="@string/ip_hint" >
|
android:hint="@string/ip_hint" >
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@
|
|||||||
android:layout_below="@+id/manuallyAddPcText"
|
android:layout_below="@+id/manuallyAddPcText"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
android:preferKeepClear="true"
|
||||||
android:text="@android:string/ok"/>
|
android:text="@android:string/ok"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:paddingTop="5dp"
|
android:paddingTop="5dp"
|
||||||
android:paddingBottom="5dp"
|
android:paddingBottom="5dp"
|
||||||
|
android:preferKeepClear="true"
|
||||||
android:textSize="28sp"/>
|
android:textSize="28sp"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:gravity="left"
|
android:gravity="left"
|
||||||
android:background="#80000000"
|
android:background="#80000000"
|
||||||
|
android:preferKeepClear="true"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:gravity="right"
|
android:gravity="right"
|
||||||
android:background="#80000000"
|
android:background="#80000000"
|
||||||
|
android:preferKeepClear="true"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
</merge>
|
</merge>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:src="@drawable/ic_settings"
|
android:src="@drawable/ic_settings"
|
||||||
|
android:preferKeepClear="true"
|
||||||
style="?android:attr/borderlessButtonStyle"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
android:layout_toRightOf="@+id/settingsButton"
|
android:layout_toRightOf="@+id/settingsButton"
|
||||||
android:layout_toEndOf="@+id/settingsButton"
|
android:layout_toEndOf="@+id/settingsButton"
|
||||||
android:src="@drawable/ic_help"
|
android:src="@drawable/ic_help"
|
||||||
|
android:preferKeepClear="true"
|
||||||
style="?android:attr/borderlessButtonStyle"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
@@ -81,6 +83,7 @@
|
|||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:src="@drawable/ic_add"
|
android:src="@drawable/ic_add"
|
||||||
|
android:preferKeepClear="true"
|
||||||
style="?android:attr/borderlessButtonStyle"/>
|
style="?android:attr/borderlessButtonStyle"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_pc_scut_background"/>
|
<background android:drawable="@color/ic_pc_scut_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_pc_scut_foreground"/>
|
<foreground android:drawable="@mipmap/ic_pc_scut_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_pc_scut_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -250,4 +250,7 @@
|
|||||||
<string name="summary_setup_guide">Afficher les instructions sur la façon de configurer votre PC de jeu pour le streaming</string>
|
<string name="summary_setup_guide">Afficher les instructions sur la façon de configurer votre PC de jeu pour le streaming</string>
|
||||||
<string name="summary_troubleshooting">Afficher des conseils pour diagnostiquer et résoudre les problèmes de streaming courants</string>
|
<string name="summary_troubleshooting">Afficher des conseils pour diagnostiquer et résoudre les problèmes de streaming courants</string>
|
||||||
<string name="pacing_balanced_alt">Équilibré avec limite FPS</string>
|
<string name="pacing_balanced_alt">Équilibré avec limite FPS</string>
|
||||||
|
<string name="title_checkbox_absolute_mouse_mode">Mode souris pour bureau à distance</string>
|
||||||
|
<string name="summary_seekbar_deadzone">Remarque : Certains jeux peuvent imposer une zone morte plus grande que celle que Moonlight est configuré pour utiliser.</string>
|
||||||
|
<string name="summary_checkbox_absolute_mouse_mode">Cela peut rendre l\'accélération de la souris plus naturelle pour l\'utilisation du bureau à distance, mais elle est incompatible avec de nombreux jeux.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -243,4 +243,15 @@
|
|||||||
<string name="perf_overlay_renderingfps">Frame rate renderizzato: %1$.2f FPS</string>
|
<string name="perf_overlay_renderingfps">Frame rate renderizzato: %1$.2f FPS</string>
|
||||||
<string name="perf_overlay_netdrops">Frame scartati dalla tua connessione di rete: %1$.2f%%</string>
|
<string name="perf_overlay_netdrops">Frame scartati dalla tua connessione di rete: %1$.2f%%</string>
|
||||||
<string name="perf_overlay_netlatency">Latenza media della rete: %1$d ms (varianza: %2$d ms)</string>
|
<string name="perf_overlay_netlatency">Latenza media della rete: %1$d ms (varianza: %2$d ms)</string>
|
||||||
|
<string name="category_help">Aiuto</string>
|
||||||
|
<string name="title_setup_guide">Guida configurazione</string>
|
||||||
|
<string name="pacing_balanced_alt">Bilanciato con il limite FPS</string>
|
||||||
|
<string name="summary_seekbar_deadzone">Nota: Alcuni giochi possono imporre una deadzone più grande di quella che Moonlight è configurato per utilizzare.</string>
|
||||||
|
<string name="title_checkbox_absolute_mouse_mode">Modalità mouse desktop remoto</string>
|
||||||
|
<string name="summary_checkbox_absolute_mouse_mode">Questo può rendere l\'accelerazione del mouse più naturale per l\'utilizzo del desktop remoto, ma è incompatibile con molti giochi.</string>
|
||||||
|
<string name="summary_setup_guide">Visualizza le istruzioni su come configurare il tuo PC da gaming per lo streaming</string>
|
||||||
|
<string name="title_troubleshooting">Guida alla risoluzione dei problemi</string>
|
||||||
|
<string name="title_privacy_policy">Informativa sulla privacy</string>
|
||||||
|
<string name="summary_troubleshooting">Visualizza i suggerimenti per la diagnosi e la risoluzione dei problemi di streaming più comuni</string>
|
||||||
|
<string name="summary_privacy_policy">Visualizza l\'informativa sulla privacy di Moonlight</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -222,7 +222,7 @@
|
|||||||
<string name="audioconf_stereo">立体声</string>
|
<string name="audioconf_stereo">立体声</string>
|
||||||
<string name="audioconf_51surround">5.1环绕声</string>
|
<string name="audioconf_51surround">5.1环绕声</string>
|
||||||
<string name="audioconf_71surround">7.1环绕声</string>
|
<string name="audioconf_71surround">7.1环绕声</string>
|
||||||
<string name="videoformat_hevcauto">如果稳定才使用HEVC</string>
|
<string name="videoformat_hevcauto">自动</string>
|
||||||
<string name="videoformat_hevcalways">强制使用HEVC(不稳定)</string>
|
<string name="videoformat_hevcalways">强制使用HEVC(不稳定)</string>
|
||||||
<string name="videoformat_hevcnever">不使用HEVC</string>
|
<string name="videoformat_hevcnever">不使用HEVC</string>
|
||||||
<string name="title_frame_pacing">视频帧速调节</string>
|
<string name="title_frame_pacing">视频帧速调节</string>
|
||||||
@@ -248,4 +248,7 @@
|
|||||||
<string name="summary_frame_pacing">指定如何平衡视频延迟和流畅度</string>
|
<string name="summary_frame_pacing">指定如何平衡视频延迟和流畅度</string>
|
||||||
<string name="pacing_balanced_alt">有FPS限制的平衡</string>
|
<string name="pacing_balanced_alt">有FPS限制的平衡</string>
|
||||||
<string name="pacing_latency">优先最低延迟</string>
|
<string name="pacing_latency">优先最低延迟</string>
|
||||||
|
<string name="summary_seekbar_deadzone">注意:有些游戏可以执行一个比Moonlight摇杆配置的更大的盲区。</string>
|
||||||
|
<string name="title_checkbox_absolute_mouse_mode">适合远程桌面的鼠标模式</string>
|
||||||
|
<string name="summary_checkbox_absolute_mouse_mode">这可以使得鼠标加速在远程桌面使用中表现得更自然,但它与许多游戏不兼容。</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
<string name="applist_refresh_title">遊戲清單</string>
|
<string name="applist_refresh_title">遊戲清單</string>
|
||||||
<string name="applist_refresh_msg">正在重新整理…</string>
|
<string name="applist_refresh_msg">正在重新整理…</string>
|
||||||
<string name="applist_refresh_error_title"> 錯誤 </string>
|
<string name="applist_refresh_error_title"> 錯誤 </string>
|
||||||
<string name="applist_refresh_error_msg">獲取遊戲清單失敗</string>
|
<string name="applist_refresh_error_msg">取得遊戲清單失敗</string>
|
||||||
<string name="applist_quit_app">正在結束</string>
|
<string name="applist_quit_app">正在結束</string>
|
||||||
<string name="applist_quit_success">結束成功</string>
|
<string name="applist_quit_success">結束成功</string>
|
||||||
<string name="applist_quit_fail">結束失敗</string>
|
<string name="applist_quit_fail">結束失敗</string>
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
<string name="title_checkbox_disable_warnings">停用錯誤訊息</string>
|
<string name="title_checkbox_disable_warnings">停用錯誤訊息</string>
|
||||||
<string name="summary_checkbox_disable_warnings">停用串流中連線錯誤訊息</string>
|
<string name="summary_checkbox_disable_warnings">停用串流中連線錯誤訊息</string>
|
||||||
<string name="title_checkbox_enable_pip">啟用子母畫面觀察模式</string>
|
<string name="title_checkbox_enable_pip">啟用子母畫面觀察模式</string>
|
||||||
<string name="summary_checkbox_enable_pip">允許多工時檢視串流畫面 (但不操作)</string>
|
<string name="summary_checkbox_enable_pip">允許多工時檢視串流畫面 (但無法控制)</string>
|
||||||
<string name="category_audio_settings">音訊設定</string>
|
<string name="category_audio_settings">音訊設定</string>
|
||||||
<string name="title_audio_config_list">環場音效組態</string>
|
<string name="title_audio_config_list">環場音效組態</string>
|
||||||
<string name="summary_audio_config_list">為家庭電影院系統啟用 5.1 或 7.1 環場音效</string>
|
<string name="summary_audio_config_list">為家庭電影院系統啟用 5.1 或 7.1 環場音效</string>
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
<string name="audioconf_51surround">5.1 環場音效</string>
|
<string name="audioconf_51surround">5.1 環場音效</string>
|
||||||
<string name="audioconf_71surround">7.1 環場音效</string>
|
<string name="audioconf_71surround">7.1 環場音效</string>
|
||||||
<string name="videoformat_hevcauto">僅在穩定時使用 HEVC</string>
|
<string name="videoformat_hevcauto">僅在穩定時使用 HEVC</string>
|
||||||
<string name="videoformat_hevcalways">強制使用 HEVC (可能會崩潰)</string>
|
<string name="videoformat_hevcalways">強制使用 HEVC (可能會當機)</string>
|
||||||
<string name="videoformat_hevcnever">不使用 HEVC</string>
|
<string name="videoformat_hevcnever">不使用 HEVC</string>
|
||||||
<string name="resolution_4k">4K</string>
|
<string name="resolution_4k">4K</string>
|
||||||
<string name="fps_30">30 FPS</string>
|
<string name="fps_30">30 FPS</string>
|
||||||
@@ -249,4 +249,7 @@
|
|||||||
<string name="title_setup_guide">設定指南</string>
|
<string name="title_setup_guide">設定指南</string>
|
||||||
<string name="summary_troubleshooting">檢視診斷和修正常見串流問題的提示</string>
|
<string name="summary_troubleshooting">檢視診斷和修正常見串流問題的提示</string>
|
||||||
<string name="summary_privacy_policy">檢視 Moonlight 的隱私權政策</string>
|
<string name="summary_privacy_policy">檢視 Moonlight 的隱私權政策</string>
|
||||||
|
<string name="summary_seekbar_deadzone">注意:有些遊戲可以強制執行一個比 Moonlight 設定大的死區。</string>
|
||||||
|
<string name="title_checkbox_absolute_mouse_mode">遠端桌面滑鼠模式</string>
|
||||||
|
<string name="summary_checkbox_absolute_mouse_mode">這可以讓滑鼠在遠端桌面使用中的加速表現更加自然,但與很多遊戲不相容。</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_label" translatable="false">Moonlight</string>
|
|
||||||
<string name="app_label_root" translatable="false">Moonlight (Root)</string>
|
|
||||||
|
|
||||||
<!-- Shortcut strings -->
|
<!-- Shortcut strings -->
|
||||||
<string name="scut_deleted_pc">PC deleted</string>
|
<string name="scut_deleted_pc">PC deleted</string>
|
||||||
<string name="scut_not_paired">PC not paired</string>
|
<string name="scut_not_paired">PC not paired</string>
|
||||||
@@ -255,7 +252,7 @@
|
|||||||
<string name="audioconf_51surround">5.1 Surround Sound</string>
|
<string name="audioconf_51surround">5.1 Surround Sound</string>
|
||||||
<string name="audioconf_71surround">7.1 Surround Sound</string>
|
<string name="audioconf_71surround">7.1 Surround Sound</string>
|
||||||
|
|
||||||
<string name="videoformat_hevcauto">Use HEVC only if stable</string>
|
<string name="videoformat_hevcauto">Automatic</string>
|
||||||
<string name="videoformat_hevcalways">Always use HEVC (may crash)</string>
|
<string name="videoformat_hevcalways">Always use HEVC (may crash)</string>
|
||||||
<string name="videoformat_hevcnever">Never use HEVC</string>
|
<string name="videoformat_hevcnever">Never use HEVC</string>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
public class LimelightBuildProps {
|
|
||||||
public static final boolean ROOT_BUILD = false;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.limelight;
|
|
||||||
|
|
||||||
public class LimelightBuildProps {
|
|
||||||
public static final boolean ROOT_BUILD = true;
|
|
||||||
}
|
|
||||||
+7
-1
@@ -43,4 +43,10 @@ This file serves to document some of the decoder errata when using MediaCodec ha
|
|||||||
- Affected decoders: Tegra X1 in Pixel C (but NOT in SHIELD TV darcy)
|
- Affected decoders: Tegra X1 in Pixel C (but NOT in SHIELD TV darcy)
|
||||||
|
|
||||||
15. Some devices that support Android 11's FEATURE_LowLatency don't support it on their first compatible H.264/HEVC decoder. It is important to examine *all* decoders for FEATURE_LowLatency before deciding on one.
|
15. Some devices that support Android 11's FEATURE_LowLatency don't support it on their first compatible H.264/HEVC decoder. It is important to examine *all* decoders for FEATURE_LowLatency before deciding on one.
|
||||||
- Affected devices: Pixel 4 (c2.qti.avc.decoder.low_latency vs c2.qti.avc.decoder) and Galaxy S21 Exynos (OMX.Exynos.avc.dec [FEATURE_LowLatency] vs C2.Exynos.avc.decoder [no FEATURE_LowLatency])
|
- Affected devices: Pixel 4 (c2.qti.avc.decoder.low_latency vs c2.qti.avc.decoder) and Galaxy S21 Exynos (OMX.Exynos.avc.dec [FEATURE_LowLatency] vs C2.Exynos.avc.decoder [no FEATURE_LowLatency])
|
||||||
|
|
||||||
|
16. Some decoder have magic undocumented MediaFormat options to enable low latency prior to the introduction of KEY_LOW_LATENCY in Android 11. See MediaCodecHelper.java for info.
|
||||||
|
- Affected devices: MediaTek, Amlogic, Amazon, Qualcomm, Exynos, Huawei
|
||||||
|
|
||||||
|
17. Fire TV 3's Amlogic HEVC decoder doesn't produce any output frames without setting the magic "vdec-lowlatency" MediaFormat option
|
||||||
|
- Affected devices: Fire TV 3
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Diese App streamt Spiele, Programme oder den kompletten Desktop von NVIDIA GameStream-kompatiebelen PCs mit NVIDIA GeForce Experience über das Lokale Netzwerk oder Internet. Gleichzeitig werden Maus-, Tastatur- und Gamepad-Eingaben von deinem Android Gerät zum PC übertragen.
|
Diese App streamt Spiele, Programme oder den kompletten Desktop von NVIDIA GameStream-kompatiebelen PCs oder <a href="https://github.com/SunshineStream/Sunshine">Sunshine Stream</a> über das Lokale Netzwerk oder Internet. Gleichzeitig werden Maus-, Tastatur- und Gamepad-Eingaben von deinem Android Gerät zum PC übertragen.
|
||||||
|
|
||||||
Die Streamingqualität kann aufgrund des verwendeten Android Geräts und der Netzwerkgegebenheiten variieren. HDR Unterstützung setzt ein HRD10-fähiges Gerät, eine GTX-1000-series GPU und HDR10-kompatibles Spiele voraus.
|
Die Streamingqualität kann aufgrund des verwendeten Android Geräts und der Netzwerkgegebenheiten variieren. HDR Unterstützung setzt ein HRD10-fähiges Gerät, eine GTX-1000-series GPU und HDR10-kompatibles Spiele voraus.
|
||||||
|
|
||||||
@@ -13,11 +13,15 @@ Die Streamingqualität kann aufgrund des verwendeten Android Geräts und der Net
|
|||||||
* Kooperatives lokales Spielen mit bis zu 4 verbundenen Eingabegeräten
|
* Kooperatives lokales Spielen mit bis zu 4 verbundenen Eingabegeräten
|
||||||
* Maussteuerung via Gamepad durch langes gedrückt halten der Starttaste
|
* Maussteuerung via Gamepad durch langes gedrückt halten der Starttaste
|
||||||
|
|
||||||
'''PC Anforderungen'''
|
'''PC Anforderungen mit NVIDIA'''
|
||||||
* NVIDIA GeForce GTX/RTX Serie GPU (''GT-Serie und AMD GPUs werden nicht von NVIDIA GameStream unterstützt'')
|
* NVIDIA GeForce GTX/RTX Serie GPU (''GT-Serie und AMD GPUs werden nicht von NVIDIA GameStream unterstützt'')
|
||||||
* Windows 7 oder neuer
|
* Windows 7 oder neuer
|
||||||
* NVIDIA GeForce Experience (GFE) 2.2.2 oder neuer
|
* NVIDIA GeForce Experience (GFE) 2.2.2 oder neuer
|
||||||
|
|
||||||
|
'''PC Anforderungen mit SunshineStream'''
|
||||||
|
* Linux, MacOS, Windows
|
||||||
|
* AMD, Nvidia GPUs oder Software-Kodierung
|
||||||
|
|
||||||
'''Anleitung zur Schnellkonfiguration'''
|
'''Anleitung zur Schnellkonfiguration'''
|
||||||
# Stellen sie sicher dass GeForce Experience auf ihrem PC installiert ist. Aktivieren sie GameStream in den SHIELD Einstellungen.
|
# Stellen sie sicher dass GeForce Experience auf ihrem PC installiert ist. Aktivieren sie GameStream in den SHIELD Einstellungen.
|
||||||
# Wählen Sie den PC in Moonlight aus und tippen sie den PIN auf ihrem PC ein.
|
# Wählen Sie den PC in Moonlight aus und tippen sie den PIN auf ihrem PC ein.
|
||||||
@@ -31,4 +35,4 @@ Besuchen die die vollständige Konfigurationsanleitung https://bit.ly/1skHFjN (E
|
|||||||
* Über das Internet bzw. LTE zu streamen
|
* Über das Internet bzw. LTE zu streamen
|
||||||
* Eine Eingabegerät das direkt mit dem PC verbunden ist zu nutzen
|
* Eine Eingabegerät das direkt mit dem PC verbunden ist zu nutzen
|
||||||
* Den kompletten Desktop zu streamen
|
* Den kompletten Desktop zu streamen
|
||||||
* Individuelle Apps zum Streamen hinzuzufügen
|
* Individuelle Apps zum Streamen hinzuzufügen
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Spiele von deinem PC auf Android spielen (nur NVIDIA)
|
Spiele von deinem PC auf Android spielen (mit NVIDIA oder SunshineStream)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
- Significantly improved video latency on some MediaTek and Amlogic devices
|
||||||
|
- Implemented themed app icon support on Android 13
|
||||||
|
- Updated community contributed translations from Weblate
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
- HEVC will now be used automatically when using settings too high for the H.264 decoder
|
||||||
|
- Rumble is now supported on Shield controller when connected to Shield Android TV devices
|
||||||
|
- Reduced latency on Chromecast with Google TV by defaulting to H.264 for streaming at 1080p or below
|
||||||
|
- Fixed an issue that prevented HEVC from being used by default on some Shield Android TV devices
|
||||||
|
- Fixed an issue where parts of the UI were not visible on some Samsung devices
|
||||||
|
- Fixed handling of non-QWERTY keyboards using new Android 13 API
|
||||||
|
- Fixed stream unexpectedly entering PiP mode when a USB permission prompt appeared
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
This app streams games, programs, or your full desktop from an NVIDIA GameStream-compatible PC on your local network or the Internet using NVIDIA GeForce Experience. Mouse, keyboard, and controller input is sent from your Android device to the PC.
|
This app streams games, programs, or your full desktop from an NVIDIA GameStream-compatible PC or <a href="https://github.com/SunshineStream/Sunshine">Sunshine Stream</a> on your local network or the Internet. Mouse, keyboard, and controller input is sent from your Android device to the PC.
|
||||||
|
|
||||||
Streaming performance may vary based on your client device and network setup. HDR requires an HDR10-capable device, GTX 1000-series GPU, and HDR10-enabled game.
|
Streaming performance may vary based on your client device and network setup. HDR requires an HDR10-capable device, GTX 1000-series GPU, and HDR10-enabled game.
|
||||||
|
|
||||||
@@ -14,11 +14,15 @@ Streaming performance may vary based on your client device and network setup. HD
|
|||||||
* Local co-op with up to 4 connected controllers
|
* Local co-op with up to 4 connected controllers
|
||||||
* Mouse control via gamepad by long-pressing Start
|
* Mouse control via gamepad by long-pressing Start
|
||||||
|
|
||||||
'''PC Requirements'''
|
'''PC Requirements for NVIDIA'''
|
||||||
* NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU
|
* NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU
|
||||||
* Windows 7 or later
|
* Windows 7 or later
|
||||||
* NVIDIA GeForce Experience or NVIDIA Quadro Experience installed
|
* NVIDIA GeForce Experience or NVIDIA Quadro Experience installed
|
||||||
|
|
||||||
|
'''PC Requirements for SunshineStream'''
|
||||||
|
* Linux, MacOS, Windows
|
||||||
|
* AMD, Nvidia GPUs or Software encoding
|
||||||
|
|
||||||
'''Quick Setup Instructions'''
|
'''Quick Setup Instructions'''
|
||||||
# Make sure GeForce/Quadro Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
|
# Make sure GeForce/Quadro Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
|
||||||
# Tap on the PC in Moonlight and type the PIN on your PC
|
# Tap on the PC in Moonlight and type the PIN on your PC
|
||||||
@@ -32,4 +36,4 @@ See the full setup guide https://bit.ly/1skHFjN for:
|
|||||||
* Streaming over the Internet or LTE
|
* Streaming over the Internet or LTE
|
||||||
* Using a controller connected directly to your PC
|
* Using a controller connected directly to your PC
|
||||||
* Streaming your full desktop
|
* Streaming your full desktop
|
||||||
* Adding custom apps to stream
|
* Adding custom apps to stream
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Play games from your PC on Android (NVIDIA-only)
|
Play games from your PC on Android (NVIDIA or SunshineStream)
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
#Mon Oct 12 13:42:18 CDT 2020
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
|
||||||
|
|||||||
@@ -1,79 +1,129 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Attempt to set APP_HOME
|
||||||
DEFAULT_JVM_OPTS=""
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn ( ) {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die ( ) {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
case "`uname`" in
|
nonstop=false
|
||||||
CYGWIN* )
|
case "$( uname )" in #(
|
||||||
cygwin=true
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
;;
|
Darwin* ) darwin=true ;; #(
|
||||||
Darwin* )
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
darwin=true
|
NONSTOP* ) nonstop=true ;;
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
|
||||||
if $cygwin ; then
|
|
||||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >&-
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >&-
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -82,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@@ -90,75 +140,95 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
warn "Could not query maximum file descriptor limit"
|
||||||
fi
|
esac
|
||||||
ulimit -n $MAX_FD
|
case $MAX_FD in #(
|
||||||
if [ $? -ne 0 ] ; then
|
'' | soft) :;; #(
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
*)
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
else
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
|
||||||
if $cygwin ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=$((i+1))
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
(0) set -- ;;
|
|
||||||
(1) set -- "$args0" ;;
|
|
||||||
(2) set -- "$args0" "$args1" ;;
|
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
function splitJvmOpts() {
|
# * args from the command line
|
||||||
JVM_OPTS=("$@")
|
# * the main class name
|
||||||
}
|
# * -classpath
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
# * -D...appname settings
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
Vendored
+26
-27
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -8,20 +24,23 @@
|
|||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS=
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -35,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -45,34 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windowz variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
goto execute
|
|
||||||
|
|
||||||
:4NT_args
|
|
||||||
@rem Get arguments from the 4NT Shell from JP Software
|
|
||||||
set CMD_LINE_ARGS=%$
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
Reference in New Issue
Block a user