Compare commits

...

43 Commits

Author SHA1 Message Date
Cameron Gutman deb78e1c64 Version 7.4 2019-06-05 23:02:06 -07:00
Cameron Gutman 9aec6b1d31 Target API 29 2019-06-05 22:59:39 -07:00
Cameron Gutman 97702b8861 Fix mouse capture after returning focus to the window on Android Q 2019-06-05 22:43:16 -07:00
Cameron Gutman 832e7197c5 Delay a bit before reporting USB devices to allow the old InputDevice to go away 2019-06-05 22:26:06 -07:00
Cameron Gutman 26b992726c Use transparent status bar and navigation bar on Android Q 2019-06-05 21:50:03 -07:00
Cameron Gutman 1cb3588841 Use low latency WifiLock on Android Q 2019-06-05 21:09:55 -07:00
Cameron Gutman b461d546d6 Use new MediaCodecInfo helper to blacklist software codecs 2019-06-05 21:05:33 -07:00
Cameron Gutman b7810d6eb6 Use the newly public InputDevice.isExternal() function on Android Q 2019-06-05 20:23:22 -07:00
Cameron Gutman 6fb3a8e57d Build with the Android Q SDK 2019-06-05 20:21:19 -07:00
Cameron Gutman b521c784bc Version 7.3.1 2019-05-27 01:48:00 -07:00
twboyii 8e1641af5f Add untranslated string in zh-rTW (#701) 2019-05-27 01:47:10 -07:00
Cameron Gutman c0aac01d33 Update AGP to 3.4.1 2019-05-27 01:43:38 -07:00
Cameron Gutman 4f8b0adcbb Fix video on GFE 3.19 2019-05-27 01:42:39 -07:00
Cameron Gutman 393a4c9c8a Fix pointer capture on Android Q Beta 3 2019-05-16 21:27:01 -07:00
Cameron Gutman 99b53f9a6a Version 7.3 2019-05-07 20:53:12 -07:00
Cameron Gutman 8da563b280 Bound queued audio data to prevent excessive latency 2019-05-07 20:39:45 -07:00
Cameron Gutman d5b950e5cf Version 7.2.1 2019-05-01 20:14:21 -07:00
Cameron Gutman c46b9acf6b Update common to fix receive time 2019-04-30 23:19:19 -07:00
Cameron Gutman d8e322bac9 Sync PC offline icon with Moonlight Qt 2019-04-30 22:27:22 -07:00
Cameron Gutman 44871626cf Version 7.2 2019-04-27 22:11:02 -07:00
Cameron Gutman f661522b5d Update moonlight-common with additional perf improvements 2019-04-27 22:00:27 -07:00
Cameron Gutman a454b0ab78 Update moonlight-common with perf improvements 2019-04-26 18:37:27 -07:00
Cameron Gutman 75bf84d0d9 Update Gradle for AS 3.4 2019-04-26 18:34:16 -07:00
Cameron Gutman c248994ed4 Version 7.1 2019-04-07 14:09:58 -07:00
Cameron Gutman a7a34ec629 Update vibration weights to match Moonlight Qt 2019-04-06 01:02:03 -07:00
Cameron Gutman 8d469c5d0a Add on-screen connection warnings 2019-04-06 00:56:45 -07:00
Cameron Gutman e6979d50b5 Update AGP to 3.3.2 2019-04-06 00:48:40 -07:00
Cameron Gutman 6e25b135a3 Update ProGuard rules to avoid slf4j warnings 2019-03-20 18:57:40 -07:00
Cameron Gutman 04e093a2c2 Update moonlight-common 2019-03-20 18:51:08 -07:00
bubuleur 813f2edd95 Update French Language (#676) 2019-03-02 20:01:21 -08:00
Cameron Gutman 337d753a33 Reduce gamepad deadzone to 7% 2019-03-02 17:23:01 -08:00
Cameron Gutman 1137c74f76 Pass AudioAttributes on L+ when vibrating 2019-03-02 17:20:39 -08:00
Cameron Gutman 0c1451f757 Improve scaling of lock icon by increasing dimensions 2019-02-18 20:46:34 -08:00
Cameron Gutman 5ab9ea48fd Version 7.0.1 2019-02-17 18:20:40 -08:00
Cameron Gutman ffcb623040 Fix crash when a rumble effect only uses the high-frequency motor 2019-02-17 18:18:00 -08:00
Cameron Gutman bfe6929642 Version 7.0 2019-02-16 19:44:45 -08:00
Cameron Gutman 50d45011a8 Add device vibration and other fixes 2019-02-16 19:13:01 -08:00
Cameron Gutman 2f7087d6d3 Stop vibration on stream end 2019-02-16 18:05:08 -08:00
Cameron Gutman 92b71588d0 Implement rumble on Android InputDevice 2019-02-16 17:56:34 -08:00
Cameron Gutman 4f3d018764 Fix OSC colliding with player 1 2019-02-16 17:29:05 -08:00
Cameron Gutman a22e33eeb9 Add rumble support for the in-app Xbox driver 2019-02-16 17:03:10 -08:00
Cameron Gutman 6a939e7495 Don't display the termination dialog for intended terminations 2019-02-10 02:28:11 -08:00
Cameron Gutman f8ba7cf190 Update common with SOPS fixes 2019-02-09 20:59:59 -08:00
26 changed files with 469 additions and 107 deletions
+5 -5
View File
@@ -1,15 +1,15 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
buildToolsVersion '28.0.3' buildToolsVersion '29.0.0'
compileSdkVersion 28 compileSdkVersion 29
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 29
versionName "6.2" versionName "7.4"
versionCode = 186 versionCode = 195
} }
flavorDimensions "root" flavorDimensions "root"
+1
View File
@@ -25,3 +25,4 @@
# jMDNS # jMDNS
-dontwarn javax.jmdns.impl.DNSCache -dontwarn javax.jmdns.impl.DNSCache
-dontwarn org.slf4j.**
+1
View File
@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+90 -14
View File
@@ -66,12 +66,14 @@ import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Locale;
public class Game extends Activity implements SurfaceHolder.Callback, public class Game extends Activity implements SurfaceHolder.Callback,
@@ -110,13 +112,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private boolean grabbedInput = true; private boolean grabbedInput = true;
private boolean grabComboDown = false; private boolean grabComboDown = false;
private StreamView streamView; private StreamView streamView;
private TextView notificationOverlayView;
private ShortcutHelper shortcutHelper; private ShortcutHelper shortcutHelper;
private MediaCodecDecoderRenderer decoderRenderer; private MediaCodecDecoderRenderer decoderRenderer;
private boolean reportedCrash; private boolean reportedCrash;
private WifiManager.WifiLock wifiLock; private WifiManager.WifiLock highPerfWifiLock;
private WifiManager.WifiLock lowLatencyWifiLock;
private boolean connectedToUsbDriverService = false; private boolean connectedToUsbDriverService = false;
private ServiceConnection usbDriverServiceConnection = new ServiceConnection() { private ServiceConnection usbDriverServiceConnection = new ServiceConnection() {
@@ -201,6 +205,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
streamView.setOnTouchListener(this); streamView.setOnTouchListener(this);
streamView.setInputCallbacks(this); streamView.setInputCallbacks(this);
notificationOverlayView = findViewById(R.id.notificationOverlay);
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this); inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -223,9 +229,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Make sure Wi-Fi is fully powered up // Make sure Wi-Fi is fully powered up
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Limelight"); highPerfWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Moonlight High Perf Lock");
wifiLock.setReferenceCounted(false); highPerfWifiLock.setReferenceCounted(false);
wifiLock.acquire(); highPerfWifiLock.acquire();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
lowLatencyWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "Moonlight Low Latency Lock");
lowLatencyWifiLock.setReferenceCounted(false);
lowLatencyWifiLock.acquire();
}
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST); String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
String appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME); String appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
@@ -424,7 +436,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (prefConfig.onscreenController) { if (prefConfig.onscreenController) {
// create virtual onscreen controller // create virtual onscreen controller
virtualController = new VirtualController(conn, virtualController = new VirtualController(controllerHandler,
(FrameLayout)streamView.getParent(), (FrameLayout)streamView.getParent(),
this); this);
virtualController.refreshLayout(); virtualController.refreshLayout();
@@ -505,8 +517,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Capture is lost when focus is lost, so it must be requested again // Capture is lost when focus is lost, so it must be requested again
// when focus is regained. // when focus is regained.
if (inputCaptureProvider.isCapturingEnabled() && hasFocus) { if (inputCaptureProvider.isCapturingEnabled() && hasFocus) {
// Recapture the pointer if focus was regained // Recapture the pointer if focus was regained. On Android Q,
streamView.requestPointerCapture(); // we have to delay a bit before requesting capture because otherwise
// we'll hit the "requestPointerCapture called for a window that has no focus"
// error and it will not actually capture the cursor.
Handler h = new Handler();
h.postDelayed(new Runnable() {
@Override
public void run() {
streamView.requestPointerCapture();
}
}, 500);
} }
} }
} }
@@ -697,7 +718,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
inputManager.unregisterInputDeviceListener(controllerHandler); inputManager.unregisterInputDeviceListener(controllerHandler);
} }
wifiLock.release(); if (lowLatencyWifiLock != null) {
lowLatencyWifiLock.release();
}
if (highPerfWifiLock != null) {
highPerfWifiLock.release();
}
if (connectedToUsbDriverService) { if (connectedToUsbDriverService) {
// Unbind from the discovery service // Unbind from the discovery service
@@ -1267,6 +1293,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (connecting || connected) { if (connecting || connected) {
connecting = connected = false; connecting = connected = false;
controllerHandler.stop();
// Stop may take a few hundred ms to do some network I/O to tell // Stop may take a few hundred ms to do some network I/O to tell
// the server we're going away and clean up. Let it run in a separate // the server we're going away and clean up. Let it run in a separate
// thread to keep things smooth for the UI. Inside moonlight-common, // thread to keep things smooth for the UI. Inside moonlight-common,
@@ -1322,8 +1350,41 @@ public class Game extends Activity implements SurfaceHolder.Callback,
LimeLog.severe("Connection terminated: " + errorCode); LimeLog.severe("Connection terminated: " + errorCode);
stopConnection(); stopConnection();
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title), // Display the error dialog if it was an unexpected termination.
getResources().getString(R.string.conn_terminated_msg), true); // Otherwise, just finish the activity immediately.
if (errorCode != 0) {
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
getResources().getString(R.string.conn_terminated_msg), true);
}
else {
finish();
}
}
}
});
}
@Override
public void connectionStatusUpdate(final int connectionStatus) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (prefConfig.disableWarnings) {
return;
}
if (connectionStatus == MoonBridge.CONN_STATUS_POOR) {
if (prefConfig.bitrate > 5000) {
notificationOverlayView.setText(getResources().getString(R.string.slow_connection_msg));
}
else {
notificationOverlayView.setText(getResources().getString(R.string.poor_connection_msg));
}
notificationOverlayView.setVisibility(View.VISIBLE);
}
else if (connectionStatus == MoonBridge.CONN_STATUS_OKAY) {
notificationOverlayView.setVisibility(View.GONE);
} }
} }
}); });
@@ -1342,10 +1403,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
connected = true; connected = true;
connecting = false; connecting = false;
// Hide the mouse cursor now. Doing it before // Hide the mouse cursor now after a short delay.
// dismissing the spinner seems to be undone // Doing it before dismissing the spinner seems to be undone
// when the spinner gets displayed. // when the spinner gets displayed. On Android Q, even now
inputCaptureProvider.enableCapture(); // is too early to capture. We will delay a second to allow
// the spinner to dismiss before capturing.
Handler h = new Handler();
h.postDelayed(new Runnable() {
@Override
public void run() {
inputCaptureProvider.enableCapture();
}
}, 500);
// Keep the display on // Keep the display on
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -1377,6 +1446,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
} }
@Override
public void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor) {
LimeLog.info(String.format((Locale)null, "Rumble on gamepad %d: %04x %04x", controllerNumber, lowFreqMotor, highFreqMotor));
controllerHandler.handleRumble(controllerNumber, lowFreqMotor, highFreqMotor);
}
@Override @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (!surfaceCreated) { if (!surfaceCreated) {
@@ -168,8 +168,17 @@ public class AndroidAudioRenderer implements AudioRenderer {
} }
@Override @Override
public void playDecodedAudio(byte[] audioData) { public void playDecodedAudio(short[] audioData) {
track.write(audioData, 0, audioData.length); // Only queue up to 40 ms of pending audio data in addition to what AudioTrack is buffering for us.
if (MoonBridge.getPendingAudioFrames() < 8) {
// This will block until the write is completed. That can cause a backlog
// of pending audio data, so we do the above check to be able to bound
// latency at 40 ms in that situation.
track.write(audioData, 0, audioData.length);
}
else {
LimeLog.info("Too many pending audio frames: " + MoonBridge.getPendingAudioFrames());
}
} }
@Override @Override
@@ -4,8 +4,11 @@ import android.content.Context;
import android.hardware.input.InputManager; import android.hardware.input.InputManager;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.media.AudioAttributes;
import android.os.Build; import android.os.Build;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.InputEvent; import android.view.InputEvent;
@@ -14,6 +17,7 @@ import android.view.MotionEvent;
import android.widget.Toast; import android.widget.Toast;
import com.limelight.LimeLog; import com.limelight.LimeLog;
import com.limelight.binding.input.driver.AbstractController;
import com.limelight.binding.input.driver.UsbDriverListener; import com.limelight.binding.input.driver.UsbDriverListener;
import com.limelight.binding.input.driver.UsbDriverService; import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.NvConnection;
@@ -51,6 +55,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final double stickDeadzone; private final double stickDeadzone;
private final InputDeviceContext defaultContext = new InputDeviceContext(); private final InputDeviceContext defaultContext = new InputDeviceContext();
private final GameGestures gestures; private final GameGestures gestures;
private final Vibrator deviceVibrator;
private boolean hasGameController; private boolean hasGameController;
private final PreferenceConfiguration prefConfig; private final PreferenceConfiguration prefConfig;
@@ -61,10 +66,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
this.conn = conn; this.conn = conn;
this.gestures = gestures; this.gestures = gestures;
this.prefConfig = prefConfig; this.prefConfig = prefConfig;
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
// HACK: For now we're hardcoding a 10% deadzone. Some deadzone // HACK: For now we're hardcoding a 7% deadzone. Some deadzone
// is required for controller batching support to work. // is required for controller batching support to work.
int deadzonePercentage = 10; int deadzonePercentage = 7;
int[] ids = InputDevice.getDeviceIds(); int[] ids = InputDevice.getDeviceIds();
for (int id : ids) { for (int id : ids) {
@@ -151,6 +157,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
onInputDeviceAdded(deviceId); onInputDeviceAdded(deviceId);
} }
public void stop() {
for (int i = 0; i < inputDeviceContexts.size(); i++) {
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
if (deviceContext.vibrator != null) {
deviceContext.vibrator.cancel();
}
}
deviceVibrator.cancel();
}
private static boolean hasJoystickAxes(InputDevice device) { private static boolean hasJoystickAxes(InputDevice device) {
return (device.getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && return (device.getSources() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK &&
getMotionRangeForJoystickAxis(device, MotionEvent.AXIS_X) != null && getMotionRangeForJoystickAxis(device, MotionEvent.AXIS_X) != null &&
@@ -206,6 +224,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
} }
if (PreferenceConfiguration.readPreferences(context).onscreenController) {
LimeLog.info("Counting OSC gamepad");
mask |= 1;
}
LimeLog.info("Enumerated "+count+" gamepads"); LimeLog.info("Enumerated "+count+" gamepads");
return mask; return mask;
} }
@@ -298,10 +321,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.assignedControllerNumber = true; context.assignedControllerNumber = true;
} }
private UsbDeviceContext createUsbDeviceContextForDevice(int deviceId) { private UsbDeviceContext createUsbDeviceContextForDevice(AbstractController device) {
UsbDeviceContext context = new UsbDeviceContext(); UsbDeviceContext context = new UsbDeviceContext();
context.id = deviceId; context.id = device.getControllerId();
context.device = device;
context.leftStickDeadzoneRadius = (float) stickDeadzone; context.leftStickDeadzoneRadius = (float) stickDeadzone;
context.rightStickDeadzoneRadius = (float) stickDeadzone; context.rightStickDeadzoneRadius = (float) stickDeadzone;
@@ -311,17 +335,23 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
private static boolean isExternal(InputDevice dev) { private static boolean isExternal(InputDevice dev) {
try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Landroid/view/InputDevice;->isExternal()Z is on the light graylist in Android P // Landroid/view/InputDevice;->isExternal()Z is officially public on Android Q
return (Boolean)dev.getClass().getMethod("isExternal").invoke(dev); return dev.isExternal();
} catch (NoSuchMethodException e) { }
e.printStackTrace(); else {
} catch (IllegalAccessException e) { try {
e.printStackTrace(); // Landroid/view/InputDevice;->isExternal()Z is on the light graylist in Android P
} catch (InvocationTargetException e) { return (Boolean)dev.getClass().getMethod("isExternal").invoke(dev);
e.printStackTrace(); } catch (NoSuchMethodException e) {
} catch (ClassCastException e) { e.printStackTrace();
e.printStackTrace(); } catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassCastException e) {
e.printStackTrace();
}
} }
// Answer true if we don't know // Answer true if we don't know
@@ -393,6 +423,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.name = devName; context.name = devName;
context.id = dev.getId(); context.id = dev.getId();
if (dev.getVibrator().hasVibrator()) {
context.vibrator = dev.getVibrator();
}
context.leftStickXAxis = MotionEvent.AXIS_X; context.leftStickXAxis = MotionEvent.AXIS_X;
context.leftStickYAxis = MotionEvent.AXIS_Y; context.leftStickYAxis = MotionEvent.AXIS_Y;
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null && if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
@@ -614,7 +648,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private short getActiveControllerMask() { private short getActiveControllerMask() {
if (prefConfig.multiController) { if (prefConfig.multiController) {
return (short)(currentControllers | initialControllers); return (short)(currentControllers | initialControllers | (prefConfig.onscreenController ? 1 : 0));
} }
else { else {
// Only Player 1 is active with multi-controller disabled // Only Player 1 is active with multi-controller disabled
@@ -1043,6 +1077,94 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
} }
private void rumbleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
// Since we can only use a single amplitude value, compute the desired amplitude
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
// NB: This value is now 0-255 as required by VibrationEffect.
short lowFreqMotorMSB = (short)((lowFreqMotor >> 8) & 0xFF);
short highFreqMotorMSB = (short)((highFreqMotor >> 8) & 0xFF);
int simulatedAmplitude = Math.min(255, (int)((lowFreqMotorMSB * 0.80) + (highFreqMotorMSB * 0.33)));
if (simulatedAmplitude == 0) {
// This case is easy - just cancel the current effect and get out.
// NB: We cannot simply check lowFreqMotor == highFreqMotor == 0
// because our simulatedAmplitude could be 0 even though our inputs
// are not (ex: lowFreqMotor == 0 && highFreqMotor == 1).
vibrator.cancel();
return;
}
// Attempt to use amplitude-based control if we're on Oreo and the device
// supports amplitude-based vibration control.
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);
return;
}
}
// If we reach this point, we don't have amplitude controls available, so
// we must emulate it by PWMing the vibration. Ick.
long pwmPeriod = 20;
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
long offTime = pwmPeriod - onTime;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.build();
vibrator.vibrate(new long[]{0, onTime, offTime}, 0, audioAttributes);
}
else {
vibrator.vibrate(new long[]{0, onTime, offTime}, 0);
}
}
public void handleRumble(short controllerNumber, short lowFreqMotor, short highFreqMotor) {
boolean foundMatchingDevice = false;
boolean vibrated = false;
for (int i = 0; i < inputDeviceContexts.size(); i++) {
InputDeviceContext deviceContext = inputDeviceContexts.valueAt(i);
if (deviceContext.controllerNumber == controllerNumber) {
foundMatchingDevice = true;
if (deviceContext.vibrator != null) {
vibrated = true;
rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
}
}
}
for (int i = 0; i < usbDeviceContexts.size(); i++) {
UsbDeviceContext deviceContext = usbDeviceContexts.valueAt(i);
if (deviceContext.controllerNumber == controllerNumber) {
foundMatchingDevice = vibrated = true;
deviceContext.device.rumble((short)lowFreqMotor, (short)highFreqMotor);
}
}
// We may decide to rumble the device for player 1
if (controllerNumber == 0) {
// If we didn't find a matching device, it must be the on-screen
// controls that triggered the rumble. Vibrate the device if
// the user has requested that behavior.
if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
}
else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
// We found a device to vibrate but it didn't have rumble support. The user
// has requested us to vibrate the device in this case.
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
}
}
}
public boolean handleButtonUp(KeyEvent event) { public boolean handleButtonUp(KeyEvent event) {
InputDeviceContext context = getContextForEvent(event); InputDeviceContext context = getContextForEvent(event);
if (context == null) { if (context == null) {
@@ -1294,12 +1416,30 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return true; return true;
} }
public void reportOscState(short buttonFlags,
short leftStickX, short leftStickY,
short rightStickX, short rightStickY,
byte leftTrigger, byte rightTrigger) {
defaultContext.leftStickX = leftStickX;
defaultContext.leftStickY = leftStickY;
defaultContext.rightStickX = rightStickX;
defaultContext.rightStickY = rightStickY;
defaultContext.leftTrigger = leftTrigger;
defaultContext.rightTrigger = rightTrigger;
defaultContext.inputMap = buttonFlags;
sendControllerInputPacket(defaultContext);
}
@Override @Override
public void reportControllerState(int controllerId, short buttonFlags, public void reportControllerState(int controllerId, short buttonFlags,
float leftStickX, float leftStickY, float leftStickX, float leftStickY,
float rightStickX, float rightStickY, float rightStickX, float rightStickY,
float leftTrigger, float rightTrigger) { float leftTrigger, float rightTrigger) {
UsbDeviceContext context = usbDeviceContexts.get(controllerId); GenericControllerContext context = usbDeviceContexts.get(controllerId);
if (context == null) { if (context == null) {
return; return;
} }
@@ -1334,19 +1474,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
} }
@Override @Override
public void deviceRemoved(int controllerId) { public void deviceRemoved(AbstractController controller) {
UsbDeviceContext context = usbDeviceContexts.get(controllerId); UsbDeviceContext context = usbDeviceContexts.get(controller.getControllerId());
if (context != null) { if (context != null) {
LimeLog.info("Removed controller: "+controllerId); LimeLog.info("Removed controller: "+controller.getControllerId());
releaseControllerNumber(context); releaseControllerNumber(context);
usbDeviceContexts.remove(controllerId); usbDeviceContexts.remove(controller.getControllerId());
} }
} }
@Override @Override
public void deviceAdded(int controllerId) { public void deviceAdded(AbstractController controller) {
UsbDeviceContext context = createUsbDeviceContextForDevice(controllerId); UsbDeviceContext context = createUsbDeviceContextForDevice(controller);
usbDeviceContexts.put(controllerId, context); usbDeviceContexts.put(controller.getControllerId(), context);
} }
class GenericControllerContext { class GenericControllerContext {
@@ -1375,6 +1515,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
class InputDeviceContext extends GenericControllerContext { class InputDeviceContext extends GenericControllerContext {
public String name; public String name;
public Vibrator vibrator;
public int leftStickXAxis = -1; public int leftStickXAxis = -1;
public int leftStickYAxis = -1; public int leftStickYAxis = -1;
@@ -1412,5 +1553,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public long startDownTime = 0; public long startDownTime = 0;
} }
class UsbDeviceContext extends GenericControllerContext {} class UsbDeviceContext extends GenericControllerContext {
public AbstractController device;
}
} }
@@ -37,11 +37,13 @@ public abstract class AbstractController {
this.listener = listener; this.listener = listener;
} }
public abstract void rumble(short lowFreqMotor, short highFreqMotor);
protected void notifyDeviceRemoved() { protected void notifyDeviceRemoved() {
listener.deviceRemoved(deviceId); listener.deviceRemoved(this);
} }
protected void notifyDeviceAdded() { protected void notifyDeviceAdded() {
listener.deviceAdded(deviceId); listener.deviceAdded(this);
} }
} }
@@ -30,6 +30,18 @@ public abstract class AbstractXboxController extends AbstractController {
private Thread createInputThread() { private Thread createInputThread() {
return new Thread() { return new Thread() {
public void run() { public void run() {
try {
// Delay for a moment before reporting the new gamepad and
// accepting new input. This allows time for the old InputDevice
// to go away before we reclaim its spot. If the old device is still
// around when we call notifyDeviceAdded(), we won't be able to claim
// the controller number used by the original InputDevice.
Thread.sleep(1000);
} catch (InterruptedException e) {}
// Report that we're added _before_ reporting input
notifyDeviceAdded();
while (!isInterrupted() && !stopped) { while (!isInterrupted() && !stopped) {
byte[] buffer = new byte[64]; byte[] buffer = new byte[64];
@@ -114,9 +126,6 @@ public abstract class AbstractXboxController extends AbstractController {
return false; return false;
} }
// Report that we're added _before_ starting the input thread
notifyDeviceAdded();
// Start listening for controller input // Start listening for controller input
inputThread = createInputThread(); inputThread = createInputThread();
inputThread.start(); inputThread.start();
@@ -131,6 +140,9 @@ public abstract class AbstractXboxController extends AbstractController {
stopped = true; stopped = true;
// Cancel any rumble effects
rumble((short)0, (short)0);
// Stop the input thread // Stop the input thread
if (inputThread != null) { if (inputThread != null) {
inputThread.interrupt(); inputThread.interrupt();
@@ -6,6 +6,6 @@ public interface UsbDriverListener {
float rightStickX, float rightStickY, float rightStickX, float rightStickY,
float leftTrigger, float rightTrigger); float leftTrigger, float rightTrigger);
void deviceRemoved(int controllerId); void deviceRemoved(AbstractController controller);
void deviceAdded(int controllerId); void deviceAdded(AbstractController controller);
} }
@@ -47,26 +47,21 @@ public class UsbDriverService extends Service implements UsbDriverListener {
} }
@Override @Override
public void deviceRemoved(int controllerId) { public void deviceRemoved(AbstractController controller) {
// Remove the the controller from our list (if not removed already) // Remove the the controller from our list (if not removed already)
for (AbstractController controller : controllers) { controllers.remove(controller);
if (controller.getControllerId() == controllerId) {
controllers.remove(controller);
break;
}
}
// Call through to the client's listener // Call through to the client's listener
if (listener != null) { if (listener != null) {
listener.deviceRemoved(controllerId); listener.deviceRemoved(controller);
} }
} }
@Override @Override
public void deviceAdded(int controllerId) { public void deviceAdded(AbstractController controller) {
// Call through to the client's listener // Call through to the client's listener
if (listener != null) { if (listener != null) {
listener.deviceAdded(controllerId); listener.deviceAdded(controller);
} }
} }
@@ -113,7 +108,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
// Report all controllerMap that already exist // Report all controllerMap that already exist
if (listener != null) { if (listener != null) {
for (AbstractController controller : controllers) { for (AbstractController controller : controllers) {
listener.deviceAdded(controller.getControllerId()); listener.deviceAdded(controller);
} }
} }
} }
@@ -139,4 +139,17 @@ public class Xbox360Controller extends AbstractXboxController {
// No need to fail init if the LED command fails // No need to fail init if the LED command fails
return true; return true;
} }
@Override
public void rumble(short lowFreqMotor, short highFreqMotor) {
byte[] data = {
0x00, 0x08, 0x00,
(byte)(lowFreqMotor >> 8), (byte)(highFreqMotor >> 8),
0x00, 0x00, 0x00
};
int res = connection.bulkTransfer(outEndpt, data, data.length, 100);
if (res != data.length) {
LimeLog.warning("Rumble transfer failed: "+res);
}
}
} }
@@ -48,6 +48,7 @@ public class XboxOneController extends AbstractXboxController {
new InitPacket(0x24c6, 0x543a, RUMBLE_INIT2), new InitPacket(0x24c6, 0x543a, RUMBLE_INIT2),
}; };
private byte seqNum = 0;
public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) { public XboxOneController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
super(device, connection, deviceId, listener); super(device, connection, deviceId, listener);
@@ -134,8 +135,6 @@ public class XboxOneController extends AbstractXboxController {
@Override @Override
protected boolean doInit() { protected boolean doInit() {
byte seqNum = 0;
// Send all applicable init packets // Send all applicable init packets
for (InitPacket pkt : INIT_PKTS) { for (InitPacket pkt : INIT_PKTS) {
if (pkt.vendorId != 0 && device.getVendorId() != pkt.vendorId) { if (pkt.vendorId != 0 && device.getVendorId() != pkt.vendorId) {
@@ -162,6 +161,20 @@ public class XboxOneController extends AbstractXboxController {
return true; return true;
} }
@Override
public void rumble(short lowFreqMotor, short highFreqMotor) {
byte[] data = {
0x09, 0x00, seqNum++, 0x09, 0x00,
0x0F, 0x00, 0x00,
(byte)(lowFreqMotor >> 9), (byte)(highFreqMotor >> 9),
(byte)0xFF, 0x00, (byte)0xFF
};
int res = connection.bulkTransfer(outEndpt, data, data.length, 100);
if (res != data.length) {
LimeLog.warning("Rumble transfer failed: "+res);
}
}
private static class InitPacket { private static class InitPacket {
final int vendorId; final int vendorId;
final int productId; final int productId;
@@ -13,6 +13,7 @@ import android.widget.RelativeLayout;
import android.widget.Toast; import android.widget.Toast;
import com.limelight.R; import com.limelight.R;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.NvConnection;
import java.util.ArrayList; import java.util.ArrayList;
@@ -38,7 +39,7 @@ public class VirtualController {
private static final boolean _PRINT_DEBUG_INFORMATION = false; private static final boolean _PRINT_DEBUG_INFORMATION = false;
private NvConnection connection = null; private ControllerHandler controllerHandler;
private Context context = null; private Context context = null;
private FrameLayout frame_layout = null; private FrameLayout frame_layout = null;
@@ -53,8 +54,8 @@ public class VirtualController {
private List<VirtualControllerElement> elements = new ArrayList<>(); private List<VirtualControllerElement> elements = new ArrayList<>();
public VirtualController(final NvConnection conn, FrameLayout layout, final Context context) { public VirtualController(final ControllerHandler controllerHandler, FrameLayout layout, final Context context) {
this.connection = conn; this.controllerHandler = controllerHandler;
this.frame_layout = layout; this.frame_layout = layout;
this.context = context; this.context = context;
@@ -173,15 +174,15 @@ public class VirtualController {
_DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY); _DBG("LEFT STICK X: " + inputContext.leftStickX + " Y: " + inputContext.leftStickY);
_DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY); _DBG("RIGHT STICK X: " + inputContext.rightStickX + " Y: " + inputContext.rightStickY);
if (connection != null) { if (controllerHandler != null) {
connection.sendControllerInput( controllerHandler.reportOscState(
inputContext.inputMap, inputContext.inputMap,
inputContext.leftTrigger,
inputContext.rightTrigger,
inputContext.leftStickX, inputContext.leftStickX,
inputContext.leftStickY, inputContext.leftStickY,
inputContext.rightStickX, inputContext.rightStickX,
inputContext.rightStickY inputContext.rightStickY,
inputContext.leftTrigger,
inputContext.rightTrigger
); );
} }
} }
@@ -474,6 +474,24 @@ public class MediaCodecHelper {
return null; return null;
} }
private static boolean isCodecBlacklisted(MediaCodecInfo codecInfo) {
// Use the new isSoftwareOnly() function on Android Q
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (codecInfo.isSoftwareOnly()) {
LimeLog.info("Skipping software-only decoder: "+codecInfo.getName());
return true;
}
}
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
return true;
}
return false;
}
public static MediaCodecInfo findFirstDecoder(String mimeType) { public static MediaCodecInfo findFirstDecoder(String mimeType) {
for (MediaCodecInfo codecInfo : getMediaCodecList()) { for (MediaCodecInfo codecInfo : getMediaCodecList()) {
@@ -482,15 +500,14 @@ public class MediaCodecHelper {
continue; continue;
} }
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports the specified video format // Find a decoder that supports the specified video format
for (String mime : codecInfo.getSupportedTypes()) { for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase(mimeType)) { if (mime.equalsIgnoreCase(mimeType)) {
// Skip blacklisted codecs
if (isCodecBlacklisted(codecInfo)) {
continue;
}
LimeLog.info("First decoder choice is "+codecInfo.getName()); LimeLog.info("First decoder choice is "+codecInfo.getName());
return codecInfo; return codecInfo;
} }
@@ -530,17 +547,16 @@ public class MediaCodecHelper {
continue; continue;
} }
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports the requested video format // Find a decoder that supports the requested video format
for (String mime : codecInfo.getSupportedTypes()) { for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase(mimeType)) { if (mime.equalsIgnoreCase(mimeType)) {
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName()); LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
// Skip blacklisted codecs
if (isCodecBlacklisted(codecInfo)) {
//continue;
}
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime); CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
if (requiredProfile != -1) { if (requiredProfile != -1) {
@@ -35,6 +35,8 @@ public class PreferenceConfiguration {
private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation"; private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation";
private static final String MOUSE_NAV_BUTTONS_STRING = "checkbox_mouse_nav_buttons"; private static final String MOUSE_NAV_BUTTONS_STRING = "checkbox_mouse_nav_buttons";
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps"; static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
static final String DEFAULT_RESOLUTION = "720p"; static final String DEFAULT_RESOLUTION = "720p";
static final String DEFAULT_FPS = "60"; static final String DEFAULT_FPS = "60";
@@ -58,6 +60,8 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_MOUSE_EMULATION = true; private static final boolean DEFAULT_MOUSE_EMULATION = true;
private static final boolean DEFAULT_MOUSE_NAV_BUTTONS = false; private static final boolean DEFAULT_MOUSE_NAV_BUTTONS = false;
private static final boolean DEFAULT_UNLOCK_FPS = false; private static final boolean DEFAULT_UNLOCK_FPS = false;
private static final boolean DEFAULT_VIBRATE_OSC = true;
private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
public static final int FORCE_H265_ON = -1; public static final int FORCE_H265_ON = -1;
public static final int AUTOSELECT_H265 = 0; public static final int AUTOSELECT_H265 = 0;
@@ -79,6 +83,8 @@ public class PreferenceConfiguration {
public boolean mouseEmulation; public boolean mouseEmulation;
public boolean mouseNavButtons; public boolean mouseNavButtons;
public boolean unlockFps; public boolean unlockFps;
public boolean vibrateOsc;
public boolean vibrateFallbackToDevice;
private static int getHeightFromResolutionString(String resString) { private static int getHeightFromResolutionString(String resString) {
if (resString.equalsIgnoreCase("360p")) { if (resString.equalsIgnoreCase("360p")) {
@@ -329,6 +335,8 @@ public class PreferenceConfiguration {
config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION); config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION);
config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS); config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS);
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS); config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
return config; return config;
} }
+8 -4
View File
@@ -1,5 +1,9 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="128dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="128dp"
<path android:fillColor="#FF000000" android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/> android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"
android:fillColor="#FFFFFF"/>
</vector> </vector>
+4 -8
View File
@@ -1,9 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector android:height="128dp" android:width="128dp" android:tint="#FFFFFF"
android:width="128dp" android:viewportHeight="24.0" android:viewportWidth="24.0"
android:height="128dp" xmlns:android="http://schemas.android.com/apk/res/android">
android:viewportWidth="24.0" <path android:fillColor="#FF000000" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
android:viewportHeight="24.0">
<path
android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"
android:fillColor="#FFFFFF"/>
</vector> </vector>
+12
View File
@@ -10,4 +10,16 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" /> android:layout_gravity="center" />
<TextView
android:id="@+id/notificationOverlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="right"
android:background="#80000000"
android:visibility="gone" />
</merge> </merge>
+5
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Shortcut strings --> <!-- Shortcut strings -->
<string name="scut_deleted_pc">PC supprimé</string> <string name="scut_deleted_pc">PC supprimé</string>
<string name="scut_not_paired">PC non appairé</string> <string name="scut_not_paired">PC non appairé</string>
@@ -133,6 +134,8 @@
<string name="category_input_settings">Paramètres d\'entrée</string> <string name="category_input_settings">Paramètres d\'entrée</string>
<string name="title_checkbox_multi_controller">Prise en charge de plusieurs contrôleurs</string> <string name="title_checkbox_multi_controller">Prise en charge de plusieurs contrôleurs</string>
<string name="summary_checkbox_multi_controller">Lorsqu\'elle n\'est pas cochée, tous les contrôleurs sont regroupés</string> <string name="summary_checkbox_multi_controller">Lorsqu\'elle n\'est pas cochée, tous les contrôleurs sont regroupés</string>
<string name="title_checkbox_vibrate_fallback">Emuler le support vibration</string>
<string name="summary_checkbox_vibrate_fallback">Vibre votre appareil pour émuler une vibration si votre manette ne le prend pas en charge</string>
<string name="title_seekbar_deadzone">Régler la zone morte du stick analogique</string> <string name="title_seekbar_deadzone">Régler la zone morte du stick analogique</string>
<string name="suffix_seekbar_deadzone">%</string> <string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Pilote de contrôleur Xbox 360/One</string> <string name="title_checkbox_xb1_driver">Pilote de contrôleur Xbox 360/One</string>
@@ -147,6 +150,8 @@
<string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string> <string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string>
<string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string> <string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string>
<string name="summary_checkbox_show_onscreen_controls">Afficher la superposition du contrôleur virtuel sur l\'écran tactile</string> <string name="summary_checkbox_show_onscreen_controls">Afficher la superposition du contrôleur virtuel sur l\'écran tactile</string>
<string name="title_checkbox_vibrate_osc">Activer les vibrations</string>
<string name="summary_checkbox_vibrate_osc">Vibre votre appareil pour émuler les vibrations des commandes à l\'écran</string>
<string name="title_only_l3r3">Montre seulement L3 et R3</string> <string name="title_only_l3r3">Montre seulement L3 et R3</string>
<string name="summary_only_l3r3">Cacher tout sauf L3 et R3</string> <string name="summary_only_l3r3">Cacher tout sauf L3 et R3</string>
<string name="title_reset_osc">Effacer la disposition des commandes à l\'écran sauvegardée</string> <string name="title_reset_osc">Effacer la disposition des commandes à l\'écran sauvegardée</string>
+6
View File
@@ -0,0 +1,6 @@
<resources>
<style name="AppBaseTheme" parent="android:Theme.Material">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>
+32 -5
View File
@@ -97,27 +97,51 @@
<string name="category_basic_settings"> 基本設置 </string> <string name="category_basic_settings"> 基本設置 </string>
<string name="title_resolution_list"> 選擇目標解析度和幀數 </string> <string name="title_resolution_list"> 選擇目標解析度和幀數 </string>
<string name="summary_resolution_list"> 過高的設定會引起串流卡頓甚至軟體閃退 </string> <string name="summary_resolution_list"> 過高的設定會引起串流卡頓甚至軟體閃退 </string>
<string name="title_seekbar_bitrate"> 選擇目標視頻碼率 </string> <string name="title_fps_list">影像幀數</string>
<string name="summary_fps_list">增加以提供更流暢的影像串流. 減少以在較低的配備上獲得較好的效能.</string>
<string name="title_seekbar_bitrate"> 選擇目標影像碼率 </string>
<string name="summary_seekbar_bitrate"> 低碼率減少卡頓,高碼率提高畫質 </string> <string name="summary_seekbar_bitrate"> 低碼率減少卡頓,高碼率提高畫質 </string>
<string name="suffix_seekbar_bitrate">Kbps</string> <string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps">解鎖所有可使用的幀率</string>
<string name="summary_unlock_fps">串流於90或120幀下可以減少高級配備的延遲, 但可能導致無法支持的配備出現延遲或崩潰</string>
<string name="title_checkbox_stretch_video"> 將畫面拉伸至全屏 </string> <string name="title_checkbox_stretch_video"> 將畫面拉伸至全屏 </string>
<string name="title_checkbox_disable_warnings"> 禁用錯誤提示 </string> <string name="title_checkbox_disable_warnings"> 禁用錯誤提示 </string>
<string name="summary_checkbox_disable_warnings"> 串流過程中禁用連接錯誤提示 </string> <string name="summary_checkbox_disable_warnings"> 串流過程中禁用連接錯誤提示 </string>
<string name="title_checkbox_enable_pip">啟用子母畫面</string>
<string name="summary_checkbox_enable_pip">當多工處理時, 允許觀看串流畫面(無法控制)</string>
<string name="category_audio_settings"> 音頻設置 </string> <string name="category_audio_settings"> 音頻設置 </string>
<string name="title_checkbox_51_surround"> 啟用 5.1 環繞音效 </string> <string name="title_checkbox_51_surround"> 啟用 5.1 環繞音效 </string>
<string name="summary_checkbox_51_surround"> 如果你的聲音聽起來有問題請禁用。\n\n需要GeForce Experience 2.7 或更高版本 </string> <string name="summary_checkbox_51_surround"> 如果你的聲音聽起來有問題請禁用。\n\n需要GeForce Experience 2.7 或更高版本 </string>
<string name="category_input_settings">輸入設定</string>
<string name="title_checkbox_multi_controller"> 啟用多手柄支持 </string> <string name="title_checkbox_multi_controller"> 啟用多手柄支持 </string>
<string name="summary_checkbox_multi_controller"> 如果禁用,所有手柄將會認作一個手柄 </string> <string name="summary_checkbox_multi_controller"> 如果禁用,所有手柄將會認作一個手柄 </string>
<string name="title_seekbar_deadzone"> 調整搖桿死區 </string> <string name="title_checkbox_vibrate_fallback">以手機模擬手柄震動</string>
<string name="summary_checkbox_vibrate_fallback">當手柄不支援震動時, 以手機來模擬</string>
<string name="title_seekbar_deadzone"> 調整手柄死區 </string>
<string name="suffix_seekbar_deadzone">%</string> <string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One 手柄驅動 </string> <string name="title_checkbox_xb1_driver">Xbox 360/One 手柄驅動 </string>
<string name="summary_checkbox_xb1_driver"> 若要在那些沒有原生Xbox手柄驅動的設備上使用Xbox手柄,請勾上此複選框 </string> <string name="summary_checkbox_xb1_driver"> 若要在那些沒有原生Xbox手柄驅動的設備上使用Xbox手柄,請勾上此複選框 </string>
<string name="title_checkbox_usb_bind_all">覆蓋Android控制器支援</string>
<string name="summary_checkbox_usb_bind_all">強制 Moonlight USB驅動程式接管所有支持的Xbox手柄</string>
<string name="title_checkbox_mouse_emulation">透過手柄模擬滑鼠</string>
<string name="summary_checkbox_mouse_emulation">長按 [Start] 鍵將會切換至滑鼠模式</string>
<string name="title_checkbox_mouse_nav_buttons">Enable back and forward mouse buttons</string>
<string name="summary_checkbox_mouse_nav_buttons">Enabling this option may break right clicking on some buggy devices</string>
<string name="category_on_screen_controls_settings">設置 </string> <string name="category_on_screen_controls_settings">設置 </string>
<string name="title_checkbox_show_onscreen_controls"> 啟用虛擬手柄 </string> <string name="title_checkbox_show_onscreen_controls"> 啟用虛擬手柄 </string>
<string name="summary_checkbox_show_onscreen_controls"> 將在串流畫面上顯示一層虛擬手柄 </string> <string name="summary_checkbox_show_onscreen_controls"> 將在串流畫面上顯示一層虛擬手柄 </string>
<string name="title_checkbox_vibrate_osc">啟用震動</string>
<string name="summary_checkbox_vibrate_osc">在手機上模擬搖桿震動</string>
<string name="title_only_l3r3">只顯示[L3]及[R3]</string>
<string name="summary_only_l3r3">隱藏所有虛擬按鈕除了L3和R3</string>
<string name="title_reset_osc">重設已經保存的觸控按鈕布局</string>
<string name="summary_reset_osc">將所有觸控按鈕重設為默認之大小和位置</string>
<string name="dialog_title_reset_osc">重設按鈕布局</string>
<string name="dialog_text_reset_osc">你確定要刪除所保存的按鈕布局嗎?</string>
<string name="toast_reset_osc_success">按鈕布局已經重設</string>
<string name="category_ui_settings"> 界面設置 </string> <string name="category_ui_settings"> 界面設置 </string>
<string name="title_language_list">語言</string> <string name="title_language_list">語言</string>
@@ -134,7 +158,10 @@
<string name="summary_checkbox_host_audio"> 將在電腦和本設備同時輸出聲音 </string> <string name="summary_checkbox_host_audio"> 將在電腦和本設備同時輸出聲音 </string>
<string name="category_advanced_settings"> 高級設置 </string> <string name="category_advanced_settings"> 高級設置 </string>
<string name="title_disable_frame_drop">永遠不掉幀</string>
<string name="summary_disable_frame_drop">在一些手機上可能可以減少micro-stuttering, 但可能導致延遲</string>
<string name="title_video_format"> H.265設置 </string> <string name="title_video_format"> H.265設置 </string>
<string name="summary_video_format">H.265能降低寬需求,但是需要設備支持 </string> <string name="summary_video_format">H.265能降低寬需求,但是需要設備支持 </string>
<string name="title_enable_hdr">啟用 HDR (實驗中)</string>
<string name="summary_enable_hdr">啟用 HDR 當遊戲與電腦的顯示卡支援時. HDR 需要 GTX 1000 系列之顯示卡或更高.</string>
</resources> </resources>
+6
View File
@@ -84,6 +84,8 @@
<string name="title_details">Details</string> <string name="title_details">Details</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="delete_pc_msg">Are you sure you want to delete this PC?</string> <string name="delete_pc_msg">Are you sure you want to delete this PC?</string>
<string name="slow_connection_msg">Slow connection to PC\nReduce your bitrate</string>
<string name="poor_connection_msg">Poor connection to PC</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="applist_connect_msg">Connecting to PC…</string> <string name="applist_connect_msg">Connecting to PC…</string>
@@ -136,6 +138,8 @@
<string name="category_input_settings">Input Settings</string> <string name="category_input_settings">Input Settings</string>
<string name="title_checkbox_multi_controller">Automatic gamepad presence detection</string> <string name="title_checkbox_multi_controller">Automatic gamepad presence detection</string>
<string name="summary_checkbox_multi_controller">Unchecking this option forces a gamepad to always be present</string> <string name="summary_checkbox_multi_controller">Unchecking this option forces a gamepad to always be present</string>
<string name="title_checkbox_vibrate_fallback">Emulate rumble support with vibration</string>
<string name="summary_checkbox_vibrate_fallback">Vibrates your device to emulate rumble if your gamepad does not support it</string>
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string> <string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
<string name="suffix_seekbar_deadzone">%</string> <string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</string> <string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</string>
@@ -150,6 +154,8 @@
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string> <string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string> <string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
<string name="summary_checkbox_show_onscreen_controls">Show virtual controller overlay on touchscreen</string> <string name="summary_checkbox_show_onscreen_controls">Show virtual controller overlay on touchscreen</string>
<string name="title_checkbox_vibrate_osc">Enable vibration</string>
<string name="summary_checkbox_vibrate_osc">Vibrates your device to emulate rumble for the on-screen controls</string>
<string name="title_only_l3r3">Only show L3 and R3</string> <string name="title_only_l3r3">Only show L3 and R3</string>
<string name="summary_only_l3r3">Hide all virtual buttons except L3 and R3</string> <string name="summary_only_l3r3">Hide all virtual buttons except L3 and R3</string>
<string name="title_reset_osc">Clear saved on-screen controls layout</string> <string name="title_reset_osc">Clear saved on-screen controls layout</string>
+16
View File
@@ -82,6 +82,11 @@
android:title="@string/title_checkbox_mouse_emulation" android:title="@string/title_checkbox_mouse_emulation"
android:summary="@string/summary_checkbox_mouse_emulation" android:summary="@string/summary_checkbox_mouse_emulation"
android:defaultValue="true" /> android:defaultValue="true" />
<CheckBoxPreference
android:key="checkbox_vibrate_fallback"
android:title="@string/title_checkbox_vibrate_fallback"
android:summary="@string/summary_checkbox_vibrate_fallback"
android:defaultValue="false" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/category_on_screen_controls_settings" <PreferenceCategory android:title="@string/category_on_screen_controls_settings"
android:key="category_onscreen_controls"> android:key="category_onscreen_controls">
@@ -90,6 +95,12 @@
android:key="checkbox_show_onscreen_controls" android:key="checkbox_show_onscreen_controls"
android:summary="@string/summary_checkbox_show_onscreen_controls" android:summary="@string/summary_checkbox_show_onscreen_controls"
android:title="@string/title_checkbox_show_onscreen_controls" /> android:title="@string/title_checkbox_show_onscreen_controls" />
<CheckBoxPreference
android:key="checkbox_vibrate_osc"
android:dependency="checkbox_show_onscreen_controls"
android:title="@string/title_checkbox_vibrate_osc"
android:summary="@string/summary_checkbox_vibrate_osc"
android:defaultValue="true" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:dependency="checkbox_show_onscreen_controls" android:dependency="checkbox_show_onscreen_controls"
@@ -138,6 +149,11 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/category_advanced_settings" <PreferenceCategory android:title="@string/category_advanced_settings"
android:key="category_advanced_settings"> android:key="category_advanced_settings">
<CheckBoxPreference
android:key="checkbox_disable_warnings"
android:title="@string/title_checkbox_disable_warnings"
android:summary="@string/summary_checkbox_disable_warnings"
android:defaultValue="false" />
<ListPreference <ListPreference
android:key="video_format" android:key="video_format"
android:title="@string/title_video_format" android:title="@string/title_video_format"
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.3.1' classpath 'com.android.tools.build:gradle:3.4.1'
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue Feb 05 20:54:22 PST 2019 #Fri Apr 26 18:29:34 PDT 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip