Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 296f97f7ca | |||
| 7a65136d29 | |||
| acaebea846 | |||
| ce850ac12f | |||
| a93422d3ed | |||
| b2e605838e | |||
| 2e14002442 | |||
| c743949df5 | |||
| f207a3f6d1 | |||
| d6211605a1 | |||
| b16676b54a | |||
| dc9bfe5189 | |||
| 80620ed4c6 | |||
| 76bd0ab696 | |||
| e0914df58a | |||
| 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 |
+17
-11
@@ -3,23 +3,26 @@ apply plugin: 'com.android.application'
|
||||
android {
|
||||
ndkVersion "23.2.8568313"
|
||||
|
||||
compileSdkVersion 32
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 32
|
||||
minSdk 16
|
||||
targetSdk 33
|
||||
|
||||
versionName "10.3"
|
||||
versionCode = 278
|
||||
versionName "10.5"
|
||||
versionCode = 282
|
||||
|
||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||
ndk.debugSymbolLevel = 'FULL'
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
flavorDimensions.add("root")
|
||||
|
||||
productFlavors {
|
||||
root {
|
||||
// Android O has native mouse capture, so don't show the rooted
|
||||
// version to devices running O on the Play Store.
|
||||
maxSdkVersion 25
|
||||
maxSdk 25
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
@@ -29,6 +32,7 @@ android {
|
||||
|
||||
applicationId "com.limelight.root"
|
||||
dimension "root"
|
||||
buildConfigField "boolean", "ROOT_BUILD", "true"
|
||||
}
|
||||
|
||||
nonRoot {
|
||||
@@ -40,6 +44,7 @@ android {
|
||||
|
||||
applicationId "com.limelight"
|
||||
dimension "root"
|
||||
buildConfigField "boolean", "ROOT_BUILD", "false"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +60,7 @@ android {
|
||||
enableSplit = false
|
||||
}
|
||||
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
|
||||
// when this split is enabled.
|
||||
enableSplit = false
|
||||
@@ -65,6 +70,8 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_label", "Moonlight (Debug)"
|
||||
resValue "string", "app_label_root", "Moonlight (Root Debug)"
|
||||
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -101,6 +108,8 @@ android {
|
||||
//
|
||||
// TL;DR: Leave the following line alone!
|
||||
applicationIdSuffix ".unofficial"
|
||||
resValue "string", "app_label", "Moonlight"
|
||||
resValue "string", "app_label_root", "Moonlight (Root)"
|
||||
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -112,9 +121,6 @@ android {
|
||||
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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -44,22 +44,31 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:installLocation="auto"
|
||||
android:gwpAsanMode="always"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<provider
|
||||
android:name=".PosterContentProvider"
|
||||
android:authorities="poster.${applicationId}"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
</provider>
|
||||
|
||||
<!-- Samsung multi-window support -->
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
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"
|
||||
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);
|
||||
|
||||
// Allow floating expanded PiP overlays while browsing apps
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
setShouldDockBigOverlays(false);
|
||||
}
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
||||
|
||||
@@ -87,7 +87,7 @@ import java.util.Locale;
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
|
||||
PerfOverlayListener
|
||||
PerfOverlayListener, UsbDriverService.UsbDriverStateListener
|
||||
{
|
||||
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 ControllerHandler controllerHandler;
|
||||
private KeyboardTranslator keyboardTranslator;
|
||||
private VirtualController virtualController;
|
||||
|
||||
private PreferenceConfiguration prefConfig;
|
||||
@@ -120,6 +121,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean autoEnterPip = false;
|
||||
private boolean surfaceCreated = false;
|
||||
private boolean attemptedConnection = false;
|
||||
private int suppressPipRefCount = 0;
|
||||
private String pcName;
|
||||
private String appName;
|
||||
|
||||
private InputCaptureProvider inputCaptureProvider;
|
||||
private int modifierFlags = 0;
|
||||
@@ -150,6 +154,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
||||
binder.setListener(controllerHandler);
|
||||
binder.setStateListener(Game.this);
|
||||
binder.start();
|
||||
connectedToUsbDriverService = true;
|
||||
}
|
||||
|
||||
@@ -269,12 +275,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
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 appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||
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);
|
||||
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
|
||||
|
||||
@@ -440,9 +447,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Initialize the connection
|
||||
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
|
||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
||||
keyboardTranslator = new KeyboardTranslator();
|
||||
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||
inputManager.registerInputDeviceListener(keyboardTranslator, null);
|
||||
|
||||
// Initialize touch contexts
|
||||
for (int i = 0; i < touchContextMap.length; i++) {
|
||||
@@ -513,6 +522,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
performanceOverlayView.setVisibility(View.GONE);
|
||||
notificationOverlayView.setVisibility(View.GONE);
|
||||
|
||||
// Update GameManager state to indicate we're in PiP (still gaming, but interruptible)
|
||||
UiHelper.notifyStreamEnteringPiP(this);
|
||||
}
|
||||
else {
|
||||
isHidingOverlays = false;
|
||||
@@ -528,6 +540,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void setPipAutoEnter(boolean autoEnter) {
|
||||
private void updatePipAutoEnter() {
|
||||
if (!prefConfig.enablePip) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean autoEnter = connected && suppressPipRefCount == 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
setPictureInPictureParams(getPictureInPictureParams(autoEnter));
|
||||
}
|
||||
@@ -865,10 +894,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
if (controllerHandler != null) {
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.unregisterInputDeviceListener(controllerHandler);
|
||||
}
|
||||
if (keyboardTranslator != null) {
|
||||
inputManager.unregisterInputDeviceListener(keyboardTranslator);
|
||||
}
|
||||
|
||||
if (lowLatencyWifiLock != null) {
|
||||
lowLatencyWifiLock.release();
|
||||
@@ -1088,7 +1120,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -1158,7 +1190,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -1428,7 +1460,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_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
|
||||
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
touchContext.setPointerCount(event.getPointerCount() - 1);
|
||||
}
|
||||
@@ -1573,11 +1613,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
private void stopConnection() {
|
||||
if (connecting || connected) {
|
||||
setPipAutoEnter(false);
|
||||
connecting = connected = false;
|
||||
updatePipAutoEnter();
|
||||
|
||||
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
|
||||
// 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,
|
||||
@@ -1738,9 +1781,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
spinner = null;
|
||||
}
|
||||
|
||||
setPipAutoEnter(true);
|
||||
connected = true;
|
||||
connecting = false;
|
||||
updatePipAutoEnter();
|
||||
|
||||
// Hide the mouse cursor now after a short delay.
|
||||
// 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
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
// Update GameManager state to indicate we're in game
|
||||
UiHelper.notifyStreamConnected(Game.this);
|
||||
|
||||
hideSystemUi(1000);
|
||||
}
|
||||
});
|
||||
@@ -1807,6 +1853,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (!attemptedConnection) {
|
||||
attemptedConnection = true;
|
||||
|
||||
// Update GameManager state to indicate we're "loading" while connecting
|
||||
UiHelper.notifyStreamConnecting(Game.this);
|
||||
|
||||
decoderRenderer.setRenderTarget(holder);
|
||||
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||
}
|
||||
@@ -1884,7 +1933,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||
short keyMap = KeyboardTranslator.translate(keyCode);
|
||||
short keyMap = keyboardTranslator.translate(keyCode, -1);
|
||||
if (keyMap != 0) {
|
||||
// handleSpecialKeys() takes the Android keycode
|
||||
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.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.window.OnBackInvokedCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
@@ -13,10 +16,26 @@ public class HelpActivity extends Activity {
|
||||
private SpinnerDialog loadingDialog;
|
||||
private WebView webView;
|
||||
|
||||
private boolean backCallbackRegistered;
|
||||
private OnBackInvokedCallback onBackInvokedCallback;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle 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);
|
||||
setContentView(webView);
|
||||
|
||||
@@ -39,6 +58,8 @@ public class HelpActivity extends Activity {
|
||||
getResources().getString(R.string.help_loading_title),
|
||||
getResources().getString(R.string.help_loading_msg), false);
|
||||
}
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,6 +68,8 @@ public class HelpActivity extends Activity {
|
||||
loadingDialog.dismiss();
|
||||
loadingDialog = null;
|
||||
}
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,7 +82,33 @@ public class HelpActivity extends Activity {
|
||||
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
|
||||
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||
public void onBackPressed() {
|
||||
// Back goes back through the WebView history
|
||||
// until no more history remains
|
||||
|
||||
@@ -124,6 +124,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
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
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class ShortcutTrampoline extends Activity {
|
||||
}
|
||||
|
||||
// Try to wake the target PC if it's offline (up to some retry limit)
|
||||
if (details.state == ComputerDetails.State.OFFLINE && --wakeHostTries >= 0) {
|
||||
if (details.state == ComputerDetails.State.OFFLINE && details.macAddress != null && --wakeHostTries >= 0) {
|
||||
try {
|
||||
// Make a best effort attempt to wake the target PC
|
||||
WakeOnLanSender.sendWolPacket(computer);
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.os.CombinedVibration;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
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.UsbDriverListener;
|
||||
import com.limelight.binding.input.driver.UsbDriverService;
|
||||
import com.limelight.binding.input.shield.ShieldControllerExtensionsHandler;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
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 GameGestures gestures;
|
||||
private final Vibrator deviceVibrator;
|
||||
private final ShieldControllerExtensionsHandler shieldControllerExtensionsHandler;
|
||||
private boolean hasGameController;
|
||||
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
@@ -71,6 +74,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
this.gestures = gestures;
|
||||
this.prefConfig = prefConfig;
|
||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
this.shieldControllerExtensionsHandler = new ShieldControllerExtensionsHandler(activityContext);
|
||||
|
||||
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||
|
||||
@@ -199,6 +203,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
deviceContext.destroy();
|
||||
}
|
||||
|
||||
shieldControllerExtensionsHandler.destroy();
|
||||
deviceVibrator.cancel();
|
||||
}
|
||||
|
||||
@@ -504,6 +509,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
LimeLog.info(dev.toString());
|
||||
|
||||
context.inputDevice = dev;
|
||||
context.name = devName;
|
||||
context.id = dev.getId();
|
||||
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) {
|
||||
@@ -1382,10 +1395,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (vibrator.hasAmplitudeControl()) {
|
||||
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
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(effect, vibrationAttributes);
|
||||
}
|
||||
else {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1395,7 +1417,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
long pwmPeriod = 20;
|
||||
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
||||
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()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
@@ -1416,10 +1445,34 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
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) {
|
||||
vibrated = true;
|
||||
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) {
|
||||
vibrated = true;
|
||||
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||
@@ -1907,6 +1960,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public String name;
|
||||
public VibratorManager vibratorManager;
|
||||
public Vibrator vibrator;
|
||||
public InputDevice inputDevice;
|
||||
public Timer rumbleRepeatTimer;
|
||||
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
@@ -1958,6 +2013,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
else if (vibrator != null) {
|
||||
vibrator.cancel();
|
||||
}
|
||||
|
||||
if (rumbleRepeatTimer != null) {
|
||||
rumbleRepeatTimer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
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 java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class to translate a Android key code into the codes GFE is expecting
|
||||
* @author Diego Waxemberg
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class KeyboardTranslator {
|
||||
public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||
|
||||
/**
|
||||
* 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_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) {
|
||||
switch (keycode)
|
||||
{
|
||||
@@ -65,10 +121,24 @@ public class KeyboardTranslator {
|
||||
/**
|
||||
* Translates the given keycode and returns the GFE keycode
|
||||
* @param keycode the code to be translated
|
||||
* @param deviceId InputDevice.getId() or -1 if unknown
|
||||
* @return a GFE keycode for the given keycode
|
||||
*/
|
||||
public static short translate(int keycode) {
|
||||
public short translate(int keycode, int deviceId) {
|
||||
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
|
||||
// and Windows VK_* codes. For all defined VK_ codes, see:
|
||||
@@ -294,4 +364,30 @@ public class KeyboardTranslator {
|
||||
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 com.limelight.BuildConfig;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
||||
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.
|
||||
// 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");
|
||||
return new ShieldCaptureProvider(activity);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
|
||||
private UsbManager usbManager;
|
||||
private PreferenceConfiguration prefConfig;
|
||||
private boolean started;
|
||||
|
||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||
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 UsbDriverListener listener;
|
||||
private UsbDriverStateListener stateListener;
|
||||
private int nextDeviceId;
|
||||
|
||||
@Override
|
||||
@@ -93,6 +95,11 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||
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 (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
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) {
|
||||
@@ -121,20 +140,29 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
if (!usbManager.hasPermission(device)) {
|
||||
// Let's ask for permission
|
||||
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
|
||||
// is indicated by calling the PendingIntent with a false result). However,
|
||||
// Samsung Knox has some policies which block this request, but rather than
|
||||
// just returning a false result or returning 0 enumerated devices,
|
||||
// they throw an undocumented SecurityException from this call, crashing
|
||||
// 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));
|
||||
} catch (SecurityException e) {
|
||||
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptCompleted();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -225,16 +253,23 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
private void start() {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
started = true;
|
||||
|
||||
// Register for USB attach broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
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
|
||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||
@@ -245,14 +280,16 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
private void stop() {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
|
||||
started = false;
|
||||
|
||||
// Stop the attachment receiver
|
||||
unregisterReceiver(receiver);
|
||||
|
||||
// Remove listeners
|
||||
listener = null;
|
||||
|
||||
// Stop all controllers
|
||||
while (controllers.size() > 0) {
|
||||
// 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
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public interface UsbDriverStateListener {
|
||||
void onUsbPermissionPromptStarting();
|
||||
void onUsbPermissionPromptCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package com.limelight.binding.input.evdev;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.BuildConfig;
|
||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||
|
||||
public class EvdevCaptureProviderShim {
|
||||
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
|
||||
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
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");
|
||||
try {
|
||||
// The docs say to call unbindService() even if the bindService() call returns false
|
||||
// or throws a SecurityException.
|
||||
if (!context.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) {
|
||||
LimeLog.info("com.nvidia.blakepairing.AccessoryService is not available on this device");
|
||||
context.unbindService(serviceConnection);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
context.unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@@ -14,6 +15,7 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
@@ -22,7 +24,8 @@ import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaCodec.CodecException;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Range;
|
||||
import android.view.Choreographer;
|
||||
@@ -87,6 +90,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
|
||||
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
|
||||
private long lastRenderedFrameTimeNanos;
|
||||
private HandlerThread choreographerHandlerThread;
|
||||
private Handler choreographerHandler;
|
||||
|
||||
private int numSpsIn;
|
||||
private int numPpsIn;
|
||||
@@ -102,6 +107,61 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
return decoder;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
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);
|
||||
if (fpsRange != null) {
|
||||
return prefs.fps <= fpsRange.getUpper();
|
||||
}
|
||||
|
||||
// Fall-through to try the Android L API if there's no performance point data
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Video size not supported at any frame rate
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// As a last resort, we will use areSizeAndRateSupported() which is explicitly NOT a
|
||||
// performance metric, but it can work at least for the purpose of determining if
|
||||
// the codec is going to die when given a stream with the specified settings.
|
||||
return caps.areSizeAndRateSupported(prefs.width, prefs.height, prefs.fps);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return !decoderCanMeetPerformancePoint(avcCaps, prefs) && decoderCanMeetPerformancePoint(hevcCaps, prefs);
|
||||
}
|
||||
else {
|
||||
// No performance data
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
||||
// Don't return anything if HEVC is forced off
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
||||
@@ -113,16 +173,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
// 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
|
||||
// for even required levels of HEVC.
|
||||
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||
if (decoderInfo != null) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, prefs)) {
|
||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
||||
MediaCodecInfo hevcDecoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||
if (hevcDecoderInfo != null) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(hevcDecoderInfo.getName(), meteredNetwork, prefs)) {
|
||||
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.
|
||||
else if (requestedHdr) {
|
||||
LimeLog.info("Forcing HEVC enabled for HDR streaming");
|
||||
}
|
||||
// > 4K streaming also requires HEVC, so force it on there too.
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr ||
|
||||
prefs.width > 4096 || prefs.height > 4096) {
|
||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
||||
else if (prefs.width > 4096 || prefs.height > 4096) {
|
||||
LimeLog.info("Forcing HEVC enabled for over 4K streaming");
|
||||
}
|
||||
// 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 {
|
||||
return null;
|
||||
@@ -130,7 +200,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
}
|
||||
}
|
||||
|
||||
return decoderInfo;
|
||||
return hevcDecoderInfo;
|
||||
}
|
||||
|
||||
public void setRenderTarget(SurfaceHolder renderTarget) {
|
||||
@@ -477,6 +547,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
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()
|
||||
{
|
||||
rendererThread = new Thread() {
|
||||
@@ -619,18 +709,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
@Override
|
||||
public void start() {
|
||||
startRendererThread();
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
startChoreographerThread();
|
||||
}
|
||||
|
||||
// !!! May be called even if setup()/start() fails !!!
|
||||
@@ -643,12 +722,15 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
rendererThread.interrupt();
|
||||
}
|
||||
|
||||
// Halt further Choreographer callbacks
|
||||
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
||||
Handler h = new Handler(Looper.getMainLooper());
|
||||
h.post(new Runnable() {
|
||||
// Post a quit message to the Choreographer looper (if we have one)
|
||||
if (choreographerHandler != null) {
|
||||
choreographerHandler.post(new Runnable() {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -660,6 +742,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
// May be called already, but we'll call it now to be safe
|
||||
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
|
||||
try {
|
||||
rendererThread.join();
|
||||
@@ -1232,6 +1328,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
str += "SOC: "+Build.SOC_MANUFACTURER+" - "+Build.SOC_MODEL+"\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 += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
||||
|
||||
@@ -136,14 +136,26 @@ public class MediaCodecHelper {
|
||||
// Exynos seems to be the only HEVC decoder that works reliably
|
||||
whitelistedHevcDecoders.add("omx.exynos");
|
||||
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
|
||||
// For some reason, other X1 implementations require bitstream fixups.
|
||||
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required. For some reason,
|
||||
// other X1 implementations require bitstream fixups. However, since numReferenceFrames
|
||||
// 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 devices with this
|
||||
// partially accelerated HEVC decoder are the Shield Tablet and Xiaomi MiPad,
|
||||
// so I'll check for those here.
|
||||
//
|
||||
// In case there are some that I missed, I will also exclude pre-Oreo OSes since
|
||||
// only Shield ATV got an Oreo update and any newer Tegra devices will not ship
|
||||
// with an old OS like Nougat.
|
||||
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") &&
|
||||
!Build.DEVICE.equalsIgnoreCase("mocha") &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
whitelistedHevcDecoders.add("omx.nvidia");
|
||||
}
|
||||
else {
|
||||
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -153,8 +165,14 @@ public class MediaCodecHelper {
|
||||
|
||||
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
|
||||
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
|
||||
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// running Android 9 or later.
|
||||
//
|
||||
// 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");
|
||||
}
|
||||
|
||||
@@ -568,20 +586,6 @@ public class MediaCodecHelper {
|
||||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
|
||||
@@ -63,7 +63,7 @@ public class NvApp {
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
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");
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
@@ -421,21 +421,18 @@ public class NvHTTP {
|
||||
|
||||
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
||||
try {
|
||||
if (verbose) {
|
||||
LimeLog.info("Requesting URL: "+getCompleteUrl(baseUrl, path, query));
|
||||
}
|
||||
|
||||
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
|
||||
String respString = resp.string();
|
||||
resp.close();
|
||||
|
||||
if (verbose) {
|
||||
if (verbose && !path.equals("serverinfo")) {
|
||||
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
|
||||
}
|
||||
|
||||
return respString;
|
||||
} catch (IOException e) {
|
||||
if (verbose) {
|
||||
if (verbose && !path.equals("serverinfo")) {
|
||||
LimeLog.warning(getCompleteUrl(baseUrl, path, query)+" -> "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.ListPreference;
|
||||
import android.provider.Settings;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class LanguagePreference extends ListPreference {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LanguagePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LanguagePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
// Launch the Android native app locale settings page
|
||||
Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setData(Uri.parse("package:" + getContext().getPackageName()));
|
||||
getContext().startActivity(intent, null);
|
||||
return;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// App locale settings should be present on all Android 13 devices,
|
||||
// but if not, we'll launch the old language chooser.
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have native app locale settings, launch the normal dialog
|
||||
super.onClick();
|
||||
}
|
||||
}
|
||||
@@ -319,6 +319,19 @@ public class PreferenceConfiguration {
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static void completeLanguagePreferenceMigration(Context context) {
|
||||
// Put our language option back to default which tells us that we've already migrated it
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE).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) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||
@@ -445,7 +458,7 @@ public class PreferenceConfiguration {
|
||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||
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.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.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
||||
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.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
@@ -79,16 +80,20 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||
public void onBackPressed() {
|
||||
finish();
|
||||
|
||||
// Check for changes that require a UI reload to take effect
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
// Language changes are handled via configuration changes in Android 13+,
|
||||
// so manual activity relaunching is no longer required.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +550,15 @@ public class StreamSettings extends Activity {
|
||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||
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
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.limelight.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.GameManager;
|
||||
import android.app.GameState;
|
||||
import android.app.LocaleManager;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -9,10 +12,12 @@ import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Insets;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.limelight.Game;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
@@ -24,25 +29,66 @@ public class UiHelper {
|
||||
private static final int TV_VERTICAL_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)
|
||||
{
|
||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// On Android 13, migrate this non-default language setting into the OS native API
|
||||
LocaleManager localeManager = activity.getSystemService(LocaleManager.class);
|
||||
localeManager.setApplicationLocales(LocaleList.forLanguageTags(locale));
|
||||
PreferenceConfiguration.completeLanguagePreferenceMigration(activity);
|
||||
}
|
||||
else {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
}
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +114,9 @@ public class UiHelper {
|
||||
View rootView = activity.findViewById(android.R.id.content);
|
||||
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) {
|
||||
// Allow this non-streaming activity to layout under notches.
|
||||
//
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
android:text="@string/title_add_pc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:preferKeepClear="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_alignParentTop="true"/>
|
||||
@@ -29,6 +30,7 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:ems="10"
|
||||
android:singleLine="true"
|
||||
android:preferKeepClear="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:hint="@string/ip_hint" >
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
android:layout_below="@+id/manuallyAddPcText"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:preferKeepClear="true"
|
||||
android:text="@android:string/ok"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
android:gravity="center"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:preferKeepClear="true"
|
||||
android:textSize="28sp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -21,6 +21,7 @@
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:gravity="left"
|
||||
android:background="#80000000"
|
||||
android:preferKeepClear="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
@@ -34,6 +35,7 @@
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:gravity="right"
|
||||
android:background="#80000000"
|
||||
android:preferKeepClear="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</merge>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:src="@drawable/ic_settings"
|
||||
android:preferKeepClear="true"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -69,6 +70,7 @@
|
||||
android:layout_toRightOf="@+id/settingsButton"
|
||||
android:layout_toEndOf="@+id/settingsButton"
|
||||
android:src="@drawable/ic_help"
|
||||
android:preferKeepClear="true"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
<ImageButton
|
||||
@@ -81,6 +83,7 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:src="@drawable/ic_add"
|
||||
android:preferKeepClear="true"
|
||||
style="?android:attr/borderlessButtonStyle"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
<string name="audioconf_stereo">Stereo</string>
|
||||
<string name="audioconf_51surround">Surround 5.1</string>
|
||||
<string name="audioconf_71surround">Surround 7.1</string>
|
||||
<string name="videoformat_hevcauto">Usa HEVC solo se stabile</string>
|
||||
<string name="videoformat_hevcauto">Automatico</string>
|
||||
<string name="videoformat_hevcalways">Usa sempre HEVC (potrebbe essere instabile)</string>
|
||||
<string name="videoformat_hevcnever">Non usare mai HEVC</string>
|
||||
<string name="title_frame_pacing">Bilanciamento frame video</string>
|
||||
@@ -243,4 +243,15 @@
|
||||
<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_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>
|
||||
@@ -61,4 +61,116 @@
|
||||
<string name="nettest_text_success">Ağınız Moonlight\'ı engelliyor gibi görünmüyor. Bağlanmakta hala sorun yaşıyorsanız, bilgisayarınızın güvenlik duvarı ayarlarını kontrol edin.
|
||||
\n
|
||||
\nİnternet üzerinden yayın yapmaya çalışıyorsanız, Moonlight İnternet Barındırma Aracını bilgisayarınıza yükleyin ve bilgisayarınızın internet bağlantısını kontrol etmek için birlikte verilen İnternet Akış Test Cihazını çalıştırın.</string>
|
||||
<string name="title_checkbox_multi_controller">Otomatik oyun kumandası varlığı algılama</string>
|
||||
<string name="summary_checkbox_multi_controller">Bu seçeneğin işaretinin kaldırılması bir oyun kumandasının her zaman mevcut olmasını zorlar</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Oyun kumandanız desteklemiyorsa, gürültüyü taklit etmek için cihazınızı titreştirir</string>
|
||||
<string name="title_seekbar_deadzone">Analog çubuk ölü bölgesini ayarlama</string>
|
||||
<string name="summary_checkbox_xb1_driver">Yerel Xbox oyun kumandası desteği olmayan cihazlar için yerleşik USB sürücüsünü etkinleştirir</string>
|
||||
<string name="title_checkbox_mouse_emulation">Oyun kumandası üzerinden fare emülasyonu</string>
|
||||
<string name="title_checkbox_mouse_nav_buttons">Geri ve ileri fare düğmelerini etkinleştirme</string>
|
||||
<string name="category_on_screen_controls_settings">Ekran Kontrolleri Ayarları</string>
|
||||
<string name="title_checkbox_show_onscreen_controls">Ekran kontrollerini göster</string>
|
||||
<string name="error_manager_not_running">ComputerManager hizmeti çalışmıyor. Lütfen birkaç saniye bekleyin veya uygulamayı yeniden başlatın.</string>
|
||||
<string name="error_unknown_host">Ana bilgisayar çözümlenemedi</string>
|
||||
<string name="title_decoding_error">Video Kod Çözücü Çöktü</string>
|
||||
<string name="message_decoding_reset">Cihazınızın video kod çözücüsü, seçtiğiniz akış ayarlarında çökmeye devam ediyor. Akış ayarlarınız varsayılana sıfırlandı.</string>
|
||||
<string name="error_usb_prohibited">USB erişimi cihaz yöneticiniz tarafından yasaklanmıştır. Knox veya MDM ayarlarınızı kontrol edin.</string>
|
||||
<string name="unable_to_pin_shortcut">Mevcut başlatıcınız sabitlenmiş kısayollar oluşturmaya izin vermiyor.</string>
|
||||
<string name="video_decoder_init_failed">Video kod çözücü başlatılamadı. Cihazınız seçilen çözünürlüğü veya kare hızını desteklemiyor olabilir.</string>
|
||||
<string name="no_video_received_error">Ana bilgisayardan video alınmadı.</string>
|
||||
<string name="no_frame_received_error">Ağ bağlantınız iyi performans göstermiyor. Video bit hızı ayarınızı düşürün veya daha hızlı bir bağlantı deneyin.</string>
|
||||
<string name="early_termination_error">Yayını başlatırken ana bilgisayarda bir şeyler ters gitti.
|
||||
\n
|
||||
\nAna bilgisayarınızda DRM korumalı herhangi bir içeriğin açık olmadığından emin olun. Ana bilgisayarınızı yeniden başlatmayı da deneyebilirsiniz.
|
||||
\n
|
||||
\nSorun devam ederse GPU sürücülerinizi ve GeForce Experience\'ı yeniden yüklemeyi deneyin.</string>
|
||||
<string name="check_ports_msg">Bağlantı noktaları için güvenlik duvarınızı ve bağlantı noktası yönlendirme kurallarınızı kontrol edin:</string>
|
||||
<string name="conn_establishing_title">Bağlantı kuruluyor</string>
|
||||
<string name="conn_terminated_title">Bağlantı sonlandırıldı</string>
|
||||
<string name="conn_terminated_msg">Bağlantı sonlandırıldı</string>
|
||||
<string name="yes">Evet</string>
|
||||
<string name="lost_connection">Bilgisayar ile bağlantı kesildi</string>
|
||||
<string name="help">Yardım</string>
|
||||
<string name="delete_pc_msg">Bu bilgisayarı silmek istediğinizden emin misiniz\?</string>
|
||||
<string name="slow_connection_msg">Bilgisayara yavaş bağlantı
|
||||
\nBit hızınızı düşürün</string>
|
||||
<string name="poor_connection_msg">Bilgisayar ile zayıf bağlantı</string>
|
||||
<string name="perf_overlay_streamdetails">Video akışı: %1$s %2$.2f FPS</string>
|
||||
<string name="perf_overlay_decoder">Kod çözücü: %1$s</string>
|
||||
<string name="perf_overlay_incomingfps">Ağdan gelen kare hızı: %1$.2f FPS</string>
|
||||
<string name="perf_overlay_renderingfps">İşleme kare hızı: %1$.2f FPS</string>
|
||||
<string name="perf_overlay_netdrops">Ağ bağlantınız tarafından düşen kare sayısı: %1$.2f%%</string>
|
||||
<string name="perf_overlay_dectime">Ortalama kod çözme süresi: %1$.2f ms</string>
|
||||
<string name="applist_menu_resume">Oturumu sürdür</string>
|
||||
<string name="applist_menu_quit">Oturumu sonlandır</string>
|
||||
<string name="applist_menu_quit_and_start">Mevcut oyundan çık ve başla</string>
|
||||
<string name="applist_menu_cancel">İptal</string>
|
||||
<string name="applist_menu_details">Detayları göster</string>
|
||||
<string name="applist_menu_scut">Kısayol oluştur</string>
|
||||
<string name="applist_menu_tv_channel">Kanala ekle</string>
|
||||
<string name="applist_menu_hide_app">Uygulamayı gizle</string>
|
||||
<string name="applist_refresh_title">Uygulama listesi</string>
|
||||
<string name="applist_quit_success">Başarıyla çıkıldı</string>
|
||||
<string name="applist_refresh_error_msg">Uygulama listesi alınamadı</string>
|
||||
<string name="applist_quit_app">Çıkılıyor</string>
|
||||
<string name="applist_quit_fail">Çıkılamadı</string>
|
||||
<string name="applist_quit_confirmation">Çalışan uygulamadan çıkmak istediğinizden emin misiniz\? Kaydedilmemiş tüm veriler kaybolacaktır.</string>
|
||||
<string name="applist_details_id">Uygulama kimliği:</string>
|
||||
<string name="msg_add_pc">Bilgisayara bağlanıyor…</string>
|
||||
<string name="addpc_success">Bilgisayar başarıyla eklendi</string>
|
||||
<string name="addpc_fail">Belirtilen bilgisayara bağlanılamıyor. Gerekli bağlantı noktalarına güvenlik duvarı üzerinden izin verildiğinden emin olun.</string>
|
||||
<string name="addpc_unknown_host">Bilgisayar adresi çözümlenemiyor. Adreste yazım hatası yapmadığınızdan emin olun.</string>
|
||||
<string name="addpc_enter_ip">Bir IP adresi girmelisiniz</string>
|
||||
<string name="category_basic_settings">Temel Ayarlar</string>
|
||||
<string name="summary_resolution_list">Görüntü netliğini artırmak için artırın. Düşük uçlu cihazlarda ve daha yavaş ağlarda daha iyi performans için azaltın.</string>
|
||||
<string name="title_native_res_dialog">Yerel çözünürlük uyarısı</string>
|
||||
<string name="title_fps_list">Video kare hızı</string>
|
||||
<string name="title_seekbar_bitrate">Video bit hızı</string>
|
||||
<string name="summary_checkbox_touchscreen_trackpad">Etkinleştirilirse, dokunmatik ekran bir dokunmatik fare gibi davranır. Devre dışı bırakılırsa, dokunmatik ekran doğrudan fare imlecini kontrol eder.</string>
|
||||
<string name="conn_establishing_msg">Bağlantı başlatılıyor</string>
|
||||
<string name="conn_metered">Uyarı: Aktif ağ bağlantınız ölçülüdür!</string>
|
||||
<string name="conn_client_latency">Ortalama kare kod çözme gecikmesi:</string>
|
||||
<string name="conn_client_latency_hw">donanım kod çözücü gecikmesi:</string>
|
||||
<string name="pcview_menu_send_wol">Yerel Ağda Uyandırma isteği gönder</string>
|
||||
<string name="applist_refresh_msg">Uygulamalar yenileniyor…</string>
|
||||
<string name="applist_refresh_error_title">Hata</string>
|
||||
<string name="title_add_pc">Bilgisayarı manuel olarak Ekle</string>
|
||||
<string name="title_resolution_list">Video çözünürlüğü</string>
|
||||
<string name="summary_fps_list">Daha akıcı bir video akışı için artırın. Düşük uçlu cihazlarda daha iyi performans için azaltın.</string>
|
||||
<string name="resolution_prefix_native">Yerel</string>
|
||||
<string name="resolution_prefix_native_fullscreen">Yerel tam ekran</string>
|
||||
<string name="category_audio_settings">Ses Ayarları</string>
|
||||
<string name="title_audio_config_list">Çevresel ses yapılandırması</string>
|
||||
<string name="summary_seekbar_deadzone">Not: Bazı oyunlar Moonlight\'ın kullanmak üzere yapılandırıldığından daha büyük bir ölü bölge uygulayabilir.</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Xbox 360/One USB oyun kumandası sürücüsü</string>
|
||||
<string name="title_checkbox_usb_bind_all">Yerel Xbox oyun kumandası desteğini geçersiz kılma</string>
|
||||
<string name="summary_checkbox_mouse_emulation">Başlat düğmesine uzun süre basmak oyun kumandasını fare moduna geçirir</string>
|
||||
<string name="summary_checkbox_mouse_nav_buttons">Bu seçeneği etkinleştirmek bazı hatalı cihazlarda sağ tıklamayı bozabilir</string>
|
||||
<string name="title_checkbox_flip_face_buttons">Yüz düğmelerini çevirme</string>
|
||||
<string name="title_checkbox_absolute_mouse_mode">Uzak masaüstü fare modu</string>
|
||||
<string name="error_404">GFE bir HTTP 404 hatası bildirdi. Bilgisayarınızın desteklenen bir GPU çalıştırdığından emin olun. Uzak masaüstü yazılımı kullanmak da bu hataya neden olabilir. Cihazınızı yeniden başlatmayı veya GFE\'yi yeniden yüklemeyi deneyin.</string>
|
||||
<string name="message_decoding_error">Moonlight, bu cihazın video kod çözücüsü ile uyumsuzluk nedeniyle çöktü. GeForce Experience\'ın bilgisayarınızdaki en son sürüme güncellendiğinden emin olun. Çökmeler devam ederse akış ayarlarını değiştirmeyi deneyin.</string>
|
||||
<string name="conn_hardware_latency">Ortalama donanım kod çözme gecikmesi:</string>
|
||||
<string name="title_decoding_reset">Video Ayarlarını Sıfırla</string>
|
||||
<string name="searching_pc">GameStream\'in çalıştığı bilgisayarlar aranıyor...
|
||||
\n
|
||||
\n GeForce Experience SHIELD ayarlarında GameStream\'in etkinleştirildiğinden emin olun.</string>
|
||||
<string name="applist_connect_msg">Bilgisayara bağlanıyor…</string>
|
||||
<string name="no">Hayır</string>
|
||||
<string name="perf_overlay_netlatency">Ortalama ağ gecikmesi: %1$d ms (varyans: %2$d ms)</string>
|
||||
<string name="addpc_wrong_sitelocal">Bu adres doğru görünmüyor. İnternet üzerinden akış için yönlendiricinizin genel IP adresini kullanmanız gerekir.</string>
|
||||
<string name="text_native_res_dialog">Yerel çözünürlük modları GeForce Experience tarafından resmi olarak desteklenmez, bu nedenle ana ekran çözünürlüğünüzü kendisi ayarlamaz. Oyun içindeyken manuel olarak ayarlamanız gerekecektir.
|
||||
\n
|
||||
\nCihazınızın çözünürlüğüne uyması için NVIDIA Denetim Masası\'nda özel bir çözünürlük oluşturmayı seçerseniz, lütfen NVIDIA\'nın olası monitör hasarı, bilgisayar kararsızlığı ve diğer olası sorunlarla ilgili uyarısını okuyup anladığınızdan emin olun.
|
||||
\n
|
||||
\nBilgisayarınızda özel bir çözünürlük oluşturulmasından kaynaklanan herhangi bir sorundan sorumlu değiliz.
|
||||
\n
|
||||
\nSon olarak, cihazınız veya bilgisayarınız doğal çözünürlükte akışı desteklemiyor olabilir. Eğer cihazınızda çalışmıyorsa, ne yazık ki şansınız yok demektir.</string>
|
||||
<string name="summary_seekbar_bitrate">Daha iyi görüntü kalitesi için artırın. Daha yavaş bağlantılarda performansı artırmak için azaltın.</string>
|
||||
<string name="title_checkbox_stretch_video">Videoyu tam ekrana genişlet</string>
|
||||
<string name="title_checkbox_vibrate_fallback">Titreşim ile gürültü desteğini taklit etme</string>
|
||||
<string name="summary_checkbox_usb_bind_all">Yerel Xbox oyun kumandası desteği mevcut olsa bile desteklenen tüm oyun kumandaları için Moonlight\'ın USB sürücüsünü kullan</string>
|
||||
<string name="summary_checkbox_flip_face_buttons">Oyun kumandaları ve ekran kontrolleri için A/B ve X/Y yüz düğmelerini değiştirir</string>
|
||||
<string name="summary_checkbox_absolute_mouse_mode">Bu fare hızlandırmanın uzak masaüstü kullanımı için daha doğal davranmasını sağlayabilir, ancak birçok oyunla uyumsuzdur.</string>
|
||||
</resources>
|
||||
@@ -222,7 +222,7 @@
|
||||
<string name="audioconf_stereo">立体声</string>
|
||||
<string name="audioconf_51surround">5.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_hevcnever">不使用HEVC</string>
|
||||
<string name="title_frame_pacing">视频帧速调节</string>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<string name="applist_menu_quit_and_start">結束目前遊戲並開始這個遊戲</string>
|
||||
<string name="applist_menu_cancel"> 取消 </string>
|
||||
<string name="applist_menu_details">檢視詳細資料</string>
|
||||
<string name="applist_menu_scut">創建捷徑</string>
|
||||
<string name="applist_menu_scut">建立捷徑</string>
|
||||
<string name="applist_menu_tv_channel">新增至頻道</string>
|
||||
<string name="applist_refresh_title">遊戲清單</string>
|
||||
<string name="applist_refresh_msg">正在重新整理…</string>
|
||||
@@ -223,7 +223,7 @@
|
||||
<string name="audioconf_stereo">立體聲</string>
|
||||
<string name="audioconf_51surround">5.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_hevcnever">不使用 HEVC</string>
|
||||
<string name="resolution_4k">4K</string>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<item>71</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Don't forget to update locales_config.xml when you modify this! -->
|
||||
<string-array name="language_names" translatable="false">
|
||||
<item>Default</item>
|
||||
<item>English</item>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_label" translatable="false">Moonlight</string>
|
||||
<string name="app_label_root" translatable="false">Moonlight (Root)</string>
|
||||
|
||||
<!-- Shortcut strings -->
|
||||
<string name="scut_deleted_pc">PC deleted</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_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_hevcnever">Never use HEVC</string>
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Don't forget to update arrays.xml when you modify this file! -->
|
||||
<locale android:name="en"/>
|
||||
<locale android:name="it"/>
|
||||
<locale android:name="ja"/>
|
||||
<locale android:name="ru"/>
|
||||
<locale android:name="nl"/>
|
||||
<locale android:name="zh-CN"/>
|
||||
<locale android:name="zh-TW"/>
|
||||
<locale android:name="ko"/>
|
||||
<locale android:name="es"/>
|
||||
<locale android:name="fr"/>
|
||||
<locale android:name="de"/>
|
||||
<locale android:name="ro"/>
|
||||
<locale android:name="uk"/>
|
||||
<locale android:name="nb-NO"/>
|
||||
<locale android:name="vi"/>
|
||||
<locale android:name="hu"/>
|
||||
<locale android:name="el"/>
|
||||
</locale-config>
|
||||
@@ -165,7 +165,7 @@
|
||||
android:title="@string/title_checkbox_enable_pip"
|
||||
android:summary="@string/summary_checkbox_enable_pip"
|
||||
android:defaultValue="false" />
|
||||
<ListPreference
|
||||
<com.limelight.preferences.LanguagePreference
|
||||
android:key="list_languages"
|
||||
android:title="@string/title_language_list"
|
||||
android:entries="@array/language_names"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
* 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'')
|
||||
* Windows 7 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'''
|
||||
# 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.
|
||||
@@ -31,4 +35,4 @@ Besuchen die die vollständige Konfigurationsanleitung https://bit.ly/1skHFjN (E
|
||||
* Über das Internet bzw. LTE zu streamen
|
||||
* Eine Eingabegerät das direkt mit dem PC verbunden ist zu nutzen
|
||||
* 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,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
|
||||
@@ -0,0 +1,4 @@
|
||||
- Implemented per-app language preferences on Android 13
|
||||
- Improved automatic HEVC selection logic on older devices
|
||||
- Fixed a few crashing bugs
|
||||
- Updated community contributed translations from Weblate
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
* Mouse control via gamepad by long-pressing Start
|
||||
|
||||
'''PC Requirements'''
|
||||
'''PC Requirements for NVIDIA'''
|
||||
* NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU
|
||||
* Windows 7 or later
|
||||
* NVIDIA GeForce Experience or NVIDIA Quadro Experience installed
|
||||
|
||||
'''PC Requirements for SunshineStream'''
|
||||
* Linux, MacOS, Windows
|
||||
* AMD, Nvidia GPUs or Software encoding
|
||||
|
||||
'''Quick Setup Instructions'''
|
||||
# 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
|
||||
@@ -32,4 +36,4 @@ See the full setup guide https://bit.ly/1skHFjN for:
|
||||
* Streaming over the Internet or LTE
|
||||
* Using a controller connected directly to your PC
|
||||
* 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
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# 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_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.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn ( ) {
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die ( ) {
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
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
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
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."
|
||||
fi
|
||||
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.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -90,75 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
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" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --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
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -8,20 +24,23 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
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
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
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
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
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_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -45,34 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
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
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@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
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
Reference in New Issue
Block a user