Compare commits

...

43 Commits

Author SHA1 Message Date
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
Cameron Gutman d1e135db4d Version 6.2 2019-02-06 22:10:29 -08:00
Cameron Gutman 61a17afe69 Fix *, @, #, and + keys on software keyboard 2019-02-06 21:40:28 -08:00
Cameron Gutman 47fd691884 Update to AGP 3.3.1 2019-02-06 21:14:50 -08:00
Cameron Gutman 0d171c6b28 Fix lock icon drawing on top of the loading spinner 2019-02-06 21:14:01 -08:00
Cameron Gutman f0c69d08b8 Add 480p option 2019-02-06 21:09:04 -08:00
Cameron Gutman 629bf5766d Fix a couple crash reports 2019-02-05 22:51:48 -08:00
Cameron Gutman 233bceeece Update common for GFE 3.17 2019-02-05 22:10:11 -08:00
Cameron Gutman 6660ea7d91 Update Xbox driver with Linux xpad.c and init quirks 2019-02-05 21:52:53 -08:00
Cameron Gutman 4864b2ca45 Add lock icon when PC is unpaired 2019-02-05 21:10:09 -08:00
Cameron Gutman 92097b318d Update Gradle and AGP 2019-02-05 20:58:49 -08:00
Cameron Gutman 997898c99d Version 6.1.3 2019-01-04 18:20:28 -08:00
Cameron Gutman 1174e03885 Fix incorrectly persisting host with missing server cert 2019-01-04 18:18:32 -08:00
Cameron Gutman ff0f54d541 Switch to using stun.moonlight-stream.org for STUN 2019-01-04 18:05:28 -08:00
Cameron Gutman 814964a100 Fix exception adding PCs 2019-01-01 23:32:16 -08:00
Cameron Gutman 7e154292a9 Stop suppressing exceptions 2019-01-01 23:31:38 -08:00
Cameron Gutman 0f9cba1053 Fix crash due to a null computer uuid 2019-01-01 22:34:27 -08:00
Cameron Gutman a4e134589d Version 6.1.1 2018-12-27 23:58:30 -08:00
Cameron Gutman cd80a94f28 Fix IllegalStateException caused by making HTTPS request without a pinned cert 2018-12-27 23:55:59 -08:00
Cameron Gutman 57c645a291 Change uuid field to String type due to new format UUIDs that fail to parse on GFE 3.16 2018-12-27 23:48:12 -08:00
38 changed files with 579 additions and 188 deletions
+2 -2
View File
@@ -8,8 +8,8 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionName "6.1" versionName "7.2"
versionCode = 182 versionCode = 191
} }
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" />
+10 -7
View File
@@ -1,8 +1,8 @@
package com.limelight; package com.limelight;
import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.List; import java.util.List;
import java.util.UUID;
import com.limelight.computers.ComputerManagerListener; import com.limelight.computers.ComputerManagerListener;
import com.limelight.computers.ComputerManagerService; import com.limelight.computers.ComputerManagerService;
@@ -28,7 +28,6 @@ import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -45,6 +44,8 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import org.xmlpull.v1.XmlPullParserException;
public class AppView extends Activity implements AdapterFragmentCallbacks { public class AppView extends Activity implements AdapterFragmentCallbacks {
private AppGridAdapter appGridAdapter; private AppGridAdapter appGridAdapter;
private String uuidString; private String uuidString;
@@ -82,7 +83,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
localBinder.waitForReady(); localBinder.waitForReady();
// Get the computer object // Get the computer object
computer = localBinder.getComputer(UUID.fromString(uuidString)); computer = localBinder.getComputer(uuidString);
if (computer == null) { if (computer == null) {
finish(); finish();
return; return;
@@ -156,7 +157,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
} }
// Don't care about other computers // Don't care about other computers
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) { if (!details.uuid.equalsIgnoreCase(uuidString)) {
return; return;
} }
@@ -180,7 +181,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
@Override @Override
public void run() { public void run() {
// Disable shortcuts referencing this PC for now // Disable shortcuts referencing this PC for now
shortcutHelper.disableShortcut(details.uuid.toString(), shortcutHelper.disableShortcut(details.uuid,
getResources().getString(R.string.scut_not_paired)); getResources().getString(R.string.scut_not_paired));
// Display a toast to the user and quit the activity // Display a toast to the user and quit the activity
@@ -216,7 +217,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
blockingLoadSpinner.dismiss(); blockingLoadSpinner.dismiss();
blockingLoadSpinner = null; blockingLoadSpinner = null;
} }
} catch (Exception ignored) {} } catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
} }
}); });
@@ -280,7 +283,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist)); List<NvApp> applist = NvHTTP.getAppListByReader(new StringReader(lastRawApplist));
updateUiWithAppList(applist); updateUiWithAppList(applist);
LimeLog.info("Loaded applist from cache"); LimeLog.info("Loaded applist from cache");
} catch (Exception e) { } catch (IOException | XmlPullParserException e) {
if (lastRawApplist != null) { if (lastRawApplist != null) {
LimeLog.warning("Saved applist corrupted: "+lastRawApplist); LimeLog.warning("Saved applist corrupted: "+lastRawApplist);
e.printStackTrace(); e.printStackTrace();
+64 -5
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();
@@ -908,7 +913,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false; return false;
} }
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState(event)); byte modifiers = getModifierState(event);
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_DOWN, modifiers);
}
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
} }
return true; return true;
@@ -959,7 +969,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false; return false;
} }
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event)); byte modifiers = getModifierState(event);
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
}
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_UP, getModifierState(event));
}
} }
return true; return true;
@@ -1255,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,
@@ -1310,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);
} }
} }
}); });
@@ -1365,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) {
+8 -5
View File
@@ -53,6 +53,8 @@ import android.widget.RelativeLayout;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import org.xmlpull.v1.XmlPullParserException;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
@@ -420,7 +422,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (XmlPullParserException | IOException e) {
e.printStackTrace(); e.printStackTrace();
message = e.getMessage(); message = e.getMessage();
} }
@@ -521,8 +523,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
message = getResources().getString(R.string.error_unknown_host); message = getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = getResources().getString(R.string.error_404); message = getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (XmlPullParserException | IOException e) {
message = e.getMessage(); message = e.getMessage();
e.printStackTrace();
} }
final String toastMessage = message; final String toastMessage = message;
@@ -548,7 +551,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Intent i = new Intent(this, AppView.class); Intent i = new Intent(this, AppView.class);
i.putExtra(AppView.NAME_EXTRA, computer.name); i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString()); i.putExtra(AppView.UUID_EXTRA, computer.uuid);
startActivity(i); startActivity(i);
} }
@@ -631,7 +634,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (details.equals(computer.details)) { if (details.equals(computer.details)) {
// Disable or delete shortcuts referencing this PC // Disable or delete shortcuts referencing this PC
shortcutHelper.disableShortcut(details.uuid.toString(), shortcutHelper.disableShortcut(details.uuid,
getResources().getString(R.string.scut_deleted_pc)); getResources().getString(R.string.scut_deleted_pc));
pcGridAdapter.removeComputer(computer); pcGridAdapter.removeComputer(computer);
@@ -662,7 +665,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Add a launcher shortcut for this PC // Add a launcher shortcut for this PC
if (details.pairState == PairState.PAIRED) { if (details.pairState == PairState.PAIRED) {
shortcutHelper.createAppViewShortcut(details.uuid.toString(), details, false); shortcutHelper.createAppViewShortcut(details.uuid, details, false);
} }
if (existingEntry != null) { if (existingEntry != null) {
@@ -48,7 +48,7 @@ public class ShortcutTrampoline extends Activity {
managerBinder = localBinder; managerBinder = localBinder;
// Get the computer object // Get the computer object
computer = managerBinder.getComputer(UUID.fromString(uuidString)); computer = managerBinder.getComputer(uuidString);
if (computer == null) { if (computer == null) {
Dialog.displayDialog(ShortcutTrampoline.this, Dialog.displayDialog(ShortcutTrampoline.this,
@@ -77,7 +77,7 @@ public class ShortcutTrampoline extends Activity {
@Override @Override
public void notifyComputerUpdated(final ComputerDetails details) { public void notifyComputerUpdated(final ComputerDetails details) {
// Don't care about other computers // Don't care about other computers
if (!details.uuid.toString().equalsIgnoreCase(uuidString)) { if (!details.uuid.equalsIgnoreCase(uuidString)) {
return; return;
} }
@@ -200,6 +200,14 @@ public class ShortcutTrampoline extends Activity {
protected boolean validateInput() { protected boolean validateInput() {
// Validate UUID // Validate UUID
if (uuidString == null) {
Dialog.displayDialog(ShortcutTrampoline.this,
getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.scut_invalid_uuid),
true);
return false;
}
try { try {
UUID.fromString(uuidString); UUID.fromString(uuidString);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
@@ -12,7 +12,6 @@ import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@@ -155,7 +154,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
} catch (Exception e) { } catch (Exception e) {
// Nothing should go wrong here // Nothing should go wrong here
e.printStackTrace(); e.printStackTrace();
return false; throw new RuntimeException(e);
} }
LimeLog.info("Generated a new key pair"); LimeLog.info("Generated a new key pair");
@@ -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;
}
} }
@@ -48,6 +48,20 @@ public class KeyboardTranslator {
public static final int VK_QUOTE = 222; public static final int VK_QUOTE = 222;
public static final int VK_PAUSE = 19; public static final int VK_PAUSE = 19;
public static boolean needsShift(int keycode) {
switch (keycode)
{
case KeyEvent.KEYCODE_AT:
case KeyEvent.KEYCODE_POUND:
case KeyEvent.KEYCODE_PLUS:
case KeyEvent.KEYCODE_STAR:
return true;
default:
return false;
}
}
/** /**
* Translates the given keycode and returns the GFE keycode * Translates the given keycode and returns the GFE keycode
* @param keycode the code to be translated * @param keycode the code to be translated
@@ -117,6 +131,7 @@ public class KeyboardTranslator {
translated = 0x0d; translated = 0x0d;
break; break;
case KeyEvent.KEYCODE_PLUS:
case KeyEvent.KEYCODE_EQUALS: case KeyEvent.KEYCODE_EQUALS:
translated = 0xbb; translated = 0xbb;
break; break;
@@ -258,6 +273,18 @@ public class KeyboardTranslator {
translated = 0x6E; translated = 0x6E;
break; break;
case KeyEvent.KEYCODE_AT:
translated = 2 + VK_0;
break;
case KeyEvent.KEYCODE_POUND:
translated = 3 + VK_0;
break;
case KeyEvent.KEYCODE_STAR:
translated = 8 + VK_0;
break;
default: default:
System.out.println("No key for "+keycode); System.out.println("No key for "+keycode);
return 0; return 0;
@@ -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);
} }
} }
} }
@@ -14,6 +14,7 @@ public class Xbox360Controller extends AbstractXboxController {
private static final int XB360_IFACE_PROTOCOL = 1; // Wired only private static final int XB360_IFACE_PROTOCOL = 1; // Wired only
private static final int[] SUPPORTED_VENDORS = { private static final int[] SUPPORTED_VENDORS = {
0x0079, // GPD Win 2
0x044f, // Thrustmaster 0x044f, // Thrustmaster
0x045e, // Microsoft 0x045e, // Microsoft
0x046d, // Logitech 0x046d, // Logitech
@@ -23,6 +24,7 @@ public class Xbox360Controller extends AbstractXboxController {
0x07ff, // Mad Catz 0x07ff, // Mad Catz
0x0e6f, // Unknown 0x0e6f, // Unknown
0x0f0d, // Hori 0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon 0x11c9, // Nacon
0x12ab, // Unknown 0x12ab, // Unknown
0x1430, // RedOctane 0x1430, // RedOctane
@@ -137,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);
}
}
} }
@@ -8,6 +8,7 @@ import com.limelight.LimeLog;
import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.ControllerPacket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
public class XboxOneController extends AbstractXboxController { public class XboxOneController extends AbstractXboxController {
@@ -23,8 +24,31 @@ public class XboxOneController extends AbstractXboxController {
0x24c6, // PowerA 0x24c6, // PowerA
}; };
// FIXME: odata_serial private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
private static final byte[] XB1_INIT_DATA = {0x05, 0x20, 0x00, 0x01, 0x00}; private static final byte[] HORI_INIT = {0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
0x00, 0x00, 0x00, (byte)0x80, 0x00};
private static final byte[] PDP_INIT1 = {0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14};
private static final byte[] PDP_INIT2 = {0x06, 0x20, 0x00, 0x02, 0x01, 0x00};
private static final byte[] RUMBLE_INIT1 = {0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
0x1D, 0x1D, (byte)0xFF, 0x00, 0x00};
private static final byte[] RUMBLE_INIT2 = {0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00};
private static InitPacket[] INIT_PKTS = {
new InitPacket(0x0e6f, 0x0165, HORI_INIT),
new InitPacket(0x0f0d, 0x0067, HORI_INIT),
new InitPacket(0x0000, 0x0000, FW2015_INIT),
new InitPacket(0x0e6f, 0x0000, PDP_INIT1),
new InitPacket(0x0e6f, 0x0000, PDP_INIT2),
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT1),
new InitPacket(0x24c6, 0x542a, RUMBLE_INIT1),
new InitPacket(0x24c6, 0x543a, RUMBLE_INIT1),
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT2),
new InitPacket(0x24c6, 0x542a, 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);
@@ -111,13 +135,55 @@ public class XboxOneController extends AbstractXboxController {
@Override @Override
protected boolean doInit() { protected boolean doInit() {
// Send the initialization packet // Send all applicable init packets
int res = connection.bulkTransfer(outEndpt, XB1_INIT_DATA, XB1_INIT_DATA.length, 3000); for (InitPacket pkt : INIT_PKTS) {
if (res != XB1_INIT_DATA.length) { if (pkt.vendorId != 0 && device.getVendorId() != pkt.vendorId) {
LimeLog.warning("Initialization transfer failed: "+res); continue;
return false; }
if (pkt.productId != 0 && device.getProductId() != pkt.productId) {
continue;
}
byte[] data = Arrays.copyOf(pkt.data, pkt.data.length);
// Populate sequence number
data[2] = seqNum++;
// Send the initialization packet
int res = connection.bulkTransfer(outEndpt, data, data.length, 3000);
if (res != data.length) {
LimeLog.warning("Initialization transfer failed: "+res);
return false;
}
} }
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 {
final int vendorId;
final int productId;
final byte[] data;
InitPacket(int vendorId, int productId, byte[] data) {
this.vendorId = vendorId;
this.productId = productId;
this.data = data;
}
}
} }
@@ -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;
@@ -167,26 +168,22 @@ public class VirtualController {
} }
void sendControllerInputContext() { void sendControllerInputContext() {
try { _DBG("INPUT_MAP + " + inputContext.inputMap);
_DBG("INPUT_MAP + " + inputContext.inputMap); _DBG("LEFT_TRIGGER " + inputContext.leftTrigger);
_DBG("LEFT_TRIGGER " + inputContext.leftTrigger); _DBG("RIGHT_TRIGGER " + inputContext.rightTrigger);
_DBG("RIGHT_TRIGGER " + inputContext.rightTrigger); _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.leftStickX,
inputContext.rightTrigger, inputContext.leftStickY,
inputContext.leftStickX, inputContext.rightStickX,
inputContext.leftStickY, inputContext.rightStickY,
inputContext.rightStickX, inputContext.leftTrigger,
inputContext.rightStickY inputContext.rightTrigger
); );
}
} catch (Exception e) {
e.printStackTrace();
} }
} }
} }
@@ -166,58 +166,54 @@ public abstract class VirtualControllerElement extends View {
} }
protected void showConfigurationDialog() { protected void showConfigurationDialog() {
try { AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext());
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext());
alertBuilder.setTitle("Configuration"); alertBuilder.setTitle("Configuration");
CharSequence functions[] = new CharSequence[]{ CharSequence functions[] = new CharSequence[]{
"Move", "Move",
"Resize", "Resize",
/*election /*election
"Set n "Set n
Disable color sormal color", Disable color sormal color",
"Set pressed color", "Set pressed color",
*/
"Cancel"
};
alertBuilder.setItems(functions, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0: { // move
actionEnableMove();
break;
}
case 1: { // resize
actionEnableResize();
break;
}
/*
case 2: { // set default color
actionShowNormalColorChooser();
break;
}
case 3: { // set pressed color
actionShowPressedColorChooser();
break;
}
*/ */
"Cancel" default: { // cancel
}; actionCancel();
alertBuilder.setItems(functions, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0: { // move
actionEnableMove();
break;
}
case 1: { // resize
actionEnableResize();
break;
}
/*
case 2: { // set default color
actionShowNormalColorChooser();
break; break;
} }
case 3: { // set pressed color
actionShowPressedColorChooser();
break;
}
*/
default: { // cancel
actionCancel();
break;
}
}
} }
}); }
AlertDialog alert = alertBuilder.create(); });
// show menu AlertDialog alert = alertBuilder.create();
alert.show(); // show menu
} catch (Exception e) { alert.show();
e.printStackTrace();
}
} }
@Override @Override
@@ -1,7 +1,6 @@
package com.limelight.computers; package com.limelight.computers;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
@@ -9,9 +8,7 @@ import java.security.cert.X509Certificate;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID;
import com.limelight.LimeLog;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import android.content.ContentValues; import android.content.ContentValues;
@@ -79,7 +76,7 @@ public class ComputerDatabaseManager {
public boolean updateComputer(ComputerDetails details) { public boolean updateComputer(ComputerDetails details) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid);
values.put(COMPUTER_NAME_COLUMN_NAME, details.name); values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress); values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress);
values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress); values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress);
@@ -102,14 +99,7 @@ public class ComputerDatabaseManager {
private ComputerDetails getComputerFromCursor(Cursor c) { private ComputerDetails getComputerFromCursor(Cursor c) {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
String uuidStr = c.getString(0); details.uuid = c.getString(0);
try {
details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) {
// We'll delete this entry
LimeLog.severe("DB: Corrupted UUID for "+details.name);
}
details.name = c.getString(1); details.name = c.getString(1);
details.localAddress = c.getString(2); details.localAddress = c.getString(2);
details.remoteAddress = c.getString(3); details.remoteAddress = c.getString(3);
@@ -152,8 +142,8 @@ public class ComputerDatabaseManager {
return computerList; return computerList;
} }
public ComputerDetails getComputerByUUID(UUID uuid) { public ComputerDetails getComputerByUUID(String uuid) {
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null); Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid }, null, null, null);
if (!c.moveToFirst()) { if (!c.moveToFirst()) {
// No matching computer // No matching computer
c.close(); c.close();
@@ -8,7 +8,6 @@ import java.net.Socket;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.limelight.LimeLog; import com.limelight.LimeLog;
@@ -18,6 +17,7 @@ import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.http.PairingManager;
import com.limelight.nvstream.mdns.MdnsComputer; import com.limelight.nvstream.mdns.MdnsComputer;
import com.limelight.nvstream.mdns.MdnsDiscoveryListener; import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
import com.limelight.utils.CacheHelper; import com.limelight.utils.CacheHelper;
@@ -241,7 +241,7 @@ public class ComputerManagerService extends Service {
return idManager.getUniqueId(); return idManager.getUniqueId();
} }
public ComputerDetails getComputer(UUID uuid) { public ComputerDetails getComputer(String uuid) {
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
if (uuid.equals(tuple.computer.uuid)) { if (uuid.equals(tuple.computer.uuid)) {
@@ -253,7 +253,7 @@ public class ComputerManagerService extends Service {
return null; return null;
} }
public void invalidateStateForComputer(UUID uuid) { public void invalidateStateForComputer(String uuid) {
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
if (uuid.equals(tuple.computer.uuid)) { if (uuid.equals(tuple.computer.uuid)) {
@@ -360,13 +360,14 @@ public class ComputerManagerService extends Service {
// Since we're on the same network, we can use STUN to find // Since we're on the same network, we can use STUN to find
// our WAN address, which is also very likely the WAN address // our WAN address, which is also very likely the WAN address
// of the PC. We can use this later to connect remotely. // of the PC. We can use this later to connect remotely.
fakeDetails.remoteAddress = NvConnection.findExternalAddressForMdns(); fakeDetails.remoteAddress = NvConnection.findExternalAddressForMdns("stun.moonlight-stream.org", 3478);
} }
// Block while we try to fill the details // Block while we try to fill the details
try { try {
runPoll(fakeDetails, true, 0); // We cannot use runPoll() here because it will attempt to persist the state of the machine
if (fakeDetails.state == ComputerDetails.State.ONLINE) { // in the database, which would be bad because we don't have our pinned cert loaded yet.
if (pollComputer(fakeDetails)) {
// See if we have record of this PC to pull its pinned cert // See if we have record of this PC to pull its pinned cert
synchronized (pollingTuples) { synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
@@ -377,10 +378,9 @@ public class ComputerManagerService extends Service {
} }
} }
if (fakeDetails.serverCert != null) { // Poll again, possibly with the pinned cert, to get accurate pairing information.
// Poll again with the pinned cert to get accurate pairing information // This will insert the host into the database too.
runPoll(fakeDetails, true, 0); runPoll(fakeDetails, true, 0);
}
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
return false; return false;
@@ -457,8 +457,12 @@ public class ComputerManagerService extends Service {
ComputerDetails newDetails = http.getComputerDetails(); ComputerDetails newDetails = http.getComputerDetails();
// Check if this is the PC we expected // Check if this is the PC we expected
if (details.uuid != null && newDetails.uuid != null && if (newDetails.uuid == null) {
!details.uuid.equals(newDetails.uuid)) { LimeLog.severe("Polling returned no UUID!");
return null;
}
// details.uuid can be null on initial PC add
else if (details.uuid != null && !details.uuid.equals(newDetails.uuid)) {
// We got the wrong PC! // We got the wrong PC!
LimeLog.info("Polling returned the wrong PC!"); LimeLog.info("Polling returned the wrong PC!");
return null; return null;
@@ -468,7 +472,8 @@ public class ComputerManagerService extends Service {
newDetails.activeAddress = address; newDetails.activeAddress = address;
return newDetails; return newDetails;
} catch (Exception e) { } catch (XmlPullParserException | IOException e) {
e.printStackTrace();
return null; return null;
} }
} }
@@ -697,8 +702,9 @@ public class ComputerManagerService extends Service {
public void run() { public void run() {
int emptyAppListResponses = 0; int emptyAppListResponses = 0;
do { do {
// Can't poll if it's not online // Can't poll if it's not online or paired
if (computer.state != ComputerDetails.State.ONLINE) { if (computer.state != ComputerDetails.State.ONLINE ||
computer.pairState != PairingManager.PairState.PAIRED) {
if (listener != null) { if (listener != null) {
listener.notifyComputerUpdated(computer); listener.notifyComputerUpdated(computer);
} }
@@ -738,12 +744,12 @@ public class ComputerManagerService extends Service {
// in a row, we'll go ahead and believe it. // in a row, we'll go ahead and believe it.
emptyAppListResponses++; emptyAppListResponses++;
} }
if (appList != null && !appList.isEmpty() && if (!appList.isEmpty() &&
(!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) { (!list.isEmpty() || emptyAppListResponses >= EMPTY_LIST_THRESHOLD)) {
// Open the cache file // Open the cache file
OutputStream cacheOut = null; OutputStream cacheOut = null;
try { try {
cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid.toString()); cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid);
CacheHelper.writeStringToOutputStream(cacheOut, appList); CacheHelper.writeStringToOutputStream(cacheOut, appList);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -770,7 +776,7 @@ public class ComputerManagerService extends Service {
listener.notifyComputerUpdated(computer); listener.notifyComputerUpdated(computer);
} }
} }
else if (appList == null || appList.isEmpty()) { else if (appList.isEmpty()) {
LimeLog.warning("Null app list received from "+computer.uuid); LimeLog.warning("Null app list received from "+computer.uuid);
} }
} catch (IOException e) { } catch (IOException e) {
@@ -12,7 +12,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID;
public class LegacyDatabaseReader { public class LegacyDatabaseReader {
private static final String COMPUTER_DB_NAME = "computers.db"; private static final String COMPUTER_DB_NAME = "computers.db";
@@ -24,14 +23,7 @@ public class LegacyDatabaseReader {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
details.name = c.getString(0); details.name = c.getString(0);
details.uuid = c.getString(1);
String uuidStr = c.getString(1);
try {
details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) {
// We'll delete this entry
LimeLog.severe("DB: Corrupted UUID for " + details.name);
}
// An earlier schema defined addresses as byte blobs. We'll // An earlier schema defined addresses as byte blobs. We'll
// gracefully migrate those to strings so we can store DNS names // gracefully migrate those to strings so we can store DNS names
@@ -9,6 +9,7 @@ import android.widget.TextView;
import com.limelight.PcView; import com.limelight.PcView;
import com.limelight.R; import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.PairingManager;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@@ -77,6 +78,14 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
overlayView.setAlpha(0.4f); overlayView.setAlpha(0.4f);
return true; return true;
} }
// We must check if the status is exactly online and unpaired
// to avoid colliding with the loading spinner when status is unknown
else if (obj.details.state == ComputerDetails.State.ONLINE &&
obj.details.pairState == PairingManager.PairState.NOT_PAIRED) {
overlayView.setImageResource(R.drawable.ic_lock);
overlayView.setAlpha(1.0f);
return true;
}
return false; return false;
} }
} }
@@ -39,7 +39,7 @@ public class DiskAssetLoader {
} }
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) { public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) {
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png"); return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
} }
// https://developer.android.com/topic/performance/graphics/load-bitmap.html // https://developer.android.com/topic/performance/graphics/load-bitmap.html
@@ -65,7 +65,7 @@ public class DiskAssetLoader {
} }
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) { public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png"); File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
// Don't bother with anything if it doesn't exist // Don't bother with anything if it doesn't exist
if (!file.exists()) { if (!file.exists()) {
@@ -137,7 +137,7 @@ public class DiskAssetLoader {
OutputStream out = null; OutputStream out = null;
boolean success = false; boolean success = false;
try { try {
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png"); out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE); CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
success = true; success = true;
} catch (IOException e) { } catch (IOException e) {
@@ -151,7 +151,7 @@ public class DiskAssetLoader {
if (!success) { if (!success) {
LimeLog.warning("Unable to populate cache with tuple: "+tuple); LimeLog.warning("Unable to populate cache with tuple: "+tuple);
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png"); CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid, tuple.app.getAppId() + ".png");
} }
} }
} }
@@ -16,7 +16,7 @@ public class MemoryAssetLoader {
}; };
private static String constructKey(CachedAppAssetLoader.LoaderTuple tuple) { private static String constructKey(CachedAppAssetLoader.LoaderTuple tuple) {
return tuple.computer.uuid.toString()+"-"+tuple.app.getAppId(); return tuple.computer.uuid+"-"+tuple.app.getAppId();
} }
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) { public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
@@ -91,14 +91,20 @@ public class AddComputerManually extends Activity {
} }
private void doAddPc(String host) { private void doAddPc(String host) {
String msg;
boolean wrongSiteLocal = false; boolean wrongSiteLocal = false;
boolean success; boolean success;
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc), SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
getResources().getString(R.string.msg_add_pc), false); getResources().getString(R.string.msg_add_pc), false);
success = managerBinder.addComputerBlocking(host, true); try {
success = managerBinder.addComputerBlocking(host, true);
} catch (IllegalArgumentException e) {
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
e.printStackTrace();
success = false;
}
if (!success){ if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host); wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
} }
@@ -3,7 +3,6 @@ package com.limelight.preferences;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.preference.DialogPreference; import android.preference.DialogPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -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,11 +83,16 @@ 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")) {
return 360; return 360;
} }
else if (resString.equalsIgnoreCase("480p")) {
return 480;
}
else if (resString.equalsIgnoreCase("720p")) { else if (resString.equalsIgnoreCase("720p")) {
return 720; return 720;
} }
@@ -103,13 +112,22 @@ public class PreferenceConfiguration {
} }
private static int getWidthFromResolutionString(String resString) { private static int getWidthFromResolutionString(String resString) {
return (getHeightFromResolutionString(resString) * 16) / 9; int height = getHeightFromResolutionString(resString);
if (height == 480) {
// This isn't an exact 16:9 resolution
return 854;
}
else {
return (height * 16) / 9;
}
} }
private static String getResolutionString(int width, int height) { private static String getResolutionString(int width, int height) {
switch (height) { switch (height) {
case 360: case 360:
return "360p"; return "360p";
case 480:
return "480p";
default: default:
case 720: case 720:
return "720p"; return "720p";
@@ -139,6 +157,9 @@ public class PreferenceConfiguration {
if (width * height <= 640 * 360) { if (width * height <= 640 * 360) {
return (int)(1000 * (fps / 30.0)); return (int)(1000 * (fps / 30.0));
} }
else if (width * height <= 854 * 480) {
return (int)(1500 * (fps / 30.0));
}
// This covers 1280x720 and 1280x800 too // This covers 1280x720 and 1280x800 too
else if (width * height <= 1366 * 768) { else if (width * height <= 1366 * 768) {
return (int)(5000 * (fps / 30.0)); return (int)(5000 * (fps / 30.0));
@@ -314,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;
} }
@@ -13,7 +13,10 @@ import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
@@ -30,7 +33,7 @@ public class ServerHelper {
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId()); intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported()); intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString()); intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid);
intent.putExtra(Game.EXTRA_PC_NAME, computer.name); intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
try { try {
if (computer.serverCert != null) { if (computer.serverCert != null) {
@@ -84,8 +87,9 @@ public class ServerHelper {
message = parent.getResources().getString(R.string.error_unknown_host); message = parent.getResources().getString(R.string.error_unknown_host);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
message = parent.getResources().getString(R.string.error_404); message = parent.getResources().getString(R.string.error_404);
} catch (Exception e) { } catch (IOException | XmlPullParserException e) {
message = e.getMessage(); message = e.getMessage();
e.printStackTrace();
} finally { } finally {
if (onComplete != null) { if (onComplete != null) {
onComplete.run(); onComplete.run();
@@ -125,7 +125,7 @@ public class ShortcutHelper {
} }
public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) { public void createAppViewShortcut(String id, ComputerDetails details, boolean forceAdd) {
createAppViewShortcut(id, details.name, details.uuid.toString(), forceAdd); createAppViewShortcut(id, details.name, details.uuid, forceAdd);
} }
@TargetApi(Build.VERSION_CODES.O) @TargetApi(Build.VERSION_CODES.O)
@@ -158,7 +158,7 @@ public class ShortcutHelper {
} }
public boolean createPinnedGameShortcut(String id, Bitmap iconBits, ComputerDetails cDetails, NvApp app) { public boolean createPinnedGameShortcut(String id, Bitmap iconBits, ComputerDetails cDetails, NvApp app) {
return createPinnedGameShortcut(id, iconBits, cDetails.name, cDetails.uuid.toString(), app.getAppName(), Integer.valueOf(app.getAppId()).toString()); return createPinnedGameShortcut(id, iconBits, cDetails.name, cDetails.uuid, app.getAppName(), Integer.valueOf(app.getAppId()).toString());
} }
public void disableShortcut(String id, CharSequence reason) { public void disableShortcut(String id, CharSequence reason) {
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
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>
+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>
+2
View File
@@ -2,6 +2,7 @@
<resources> <resources>
<string-array name="resolution_names"> <string-array name="resolution_names">
<item>360p</item> <item>360p</item>
<item>480p</item>
<item>720p</item> <item>720p</item>
<item>1080p</item> <item>1080p</item>
<item>1440p</item> <item>1440p</item>
@@ -9,6 +10,7 @@
</string-array> </string-array>
<string-array name="resolution_values" translatable="false"> <string-array name="resolution_values" translatable="false">
<item>360p</item> <item>360p</item>
<item>480p</item>
<item>720p</item> <item>720p</item>
<item>1080p</item> <item>1080p</item>
<item>1440p</item> <item>1440p</item>
+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.2.1' classpath 'com.android.tools.build:gradle:3.4.0'
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue May 08 18:56:31 PDT 2018 #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.6-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip