Compare commits

...

29 Commits

Author SHA1 Message Date
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
23 changed files with 331 additions and 62 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionName "6.2" versionName "7.3"
versionCode = 186 versionCode = 193
} }
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" />
+50 -3
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,6 +112,7 @@ 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;
@@ -201,6 +204,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) {
@@ -424,7 +429,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();
@@ -1267,6 +1272,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 +1329,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);
} }
} }
}); });
@@ -1377,6 +1417,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;
@@ -393,6 +417,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 +642,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 +1071,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 +1410,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 +1468,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 +1509,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 +1547,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);
} }
} }
@@ -131,6 +131,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
); );
} }
} }
@@ -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
@@ -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.0'
} }
} }
+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