Compare commits

..
34 Commits
Author SHA1 Message Date
Cameron Gutman 3de9765eaa Version 5.10 2018-10-27 23:45:01 -07:00
Cameron Gutman d4072eb295 Avoid nulling activeAddress during polling 2018-10-27 23:38:11 -07:00
Cameron Gutman cac2bdbb81 Disable back mouse button on Xiaomi devices to workaround issue 2018-10-27 13:50:37 -07:00
Cameron Gutman 66f0aee3f8 Use STUN to discover WAN address when PC is found using mDNS 2018-10-27 10:46:28 -07:00
Cameron Gutman b690dc5474 Rewrite reachability code and computer DB to bring it inline with other modern Moonlight clients 2018-10-27 02:18:33 -07:00
Cameron Gutman c2fbe6ad91 Version 5.9.4 2018-10-24 19:41:39 -07:00
Cameron Gutman cf07c02398 Update AGP to 3.2.1 2018-10-24 19:40:26 -07:00
Cameron Gutman 42dc928ad5 Fixes to make the translation build without warnings 2018-10-24 19:37:41 -07:00
bubuleur 11597f0aa7 Update of the French version (#636)
* Update of the French version

Hello,
Update your application in French
thank you

* Update strings.xml

* Update strings.xml
2018-10-24 19:36:50 -07:00
Cameron Gutman cdcd4d48f2 Always handle KEYCODE_BACK to prevent synthetic right-clicks on back. Possibly fixes #634 2018-10-24 19:25:47 -07:00
Cameron Gutman a9af4e54a9 Add confirmation dialog for PC deletion 2018-10-24 18:47:52 -07:00
Cameron Gutman 7eac609219 Fix root mouse capture with su binaries that don't like additional parameters after -c 2018-10-10 21:23:41 -07:00
Cameron Gutman fa761debc4 Fix root build 2018-10-05 01:44:03 -07:00
Cameron Gutman 62e175f069 Avoid crashing when opening an app context menu in list mode 2018-10-05 01:42:19 -07:00
Cameron Gutman d7d8c40565 Version 5.9.3 2018-10-05 01:34:59 -07:00
Cameron Gutman 64de13ab50 Try to disambiguate right clicks from back presses 2018-10-05 01:29:18 -07:00
Cameron Gutman 2f02939638 Always process key events before the IME 2018-10-05 01:10:27 -07:00
Cameron Gutman 1d7c8697e9 Add support for X1 and X2 mouse buttons 2018-10-05 00:56:30 -07:00
Cameron Gutman 7dea322bbd Update build tools to 28.0.3 2018-09-29 15:37:31 -07:00
Cameron Gutman 349ecb16ab Increment version code 2018-09-29 15:36:21 -07:00
Cameron Gutman a3867735c1 Update to AGP 3.2 2018-09-29 15:31:13 -07:00
Cameron Gutman 5b087e9f70 Update common-c with split encode change 2018-09-22 20:22:59 -07:00
Cameron Gutman eed18223eb Version 5.9.2 2018-09-18 20:36:53 -07:00
Cameron Gutman 30d4d2a918 Update moonlight-common 2018-09-18 20:27:08 -07:00
Cameron Gutman 30f666c70e Update AGP to 3.2 rc3 2018-09-18 20:23:25 -07:00
Cameron Gutman 209fead0e8 Only add the create shortcut option if the box art is present to avoid crashing 2018-09-18 20:22:16 -07:00
Cameron Gutman 5c6889bf6d Version 5.9.1 2018-08-12 00:45:19 -07:00
Cameron Gutman 7d24900756 Update build tools to 28.0.2 2018-08-12 00:44:09 -07:00
Cameron Gutman 79a75b9d19 Update common with audio fix and game launch fix 2018-08-12 00:40:56 -07:00
Cameron Gutman 29b64992bd Enable stale and no-response bots to reduce inactive issues 2018-08-12 00:20:03 -07:00
Cameron Gutman c9b14540f2 Remove reference to old Moonlight Java project 2018-08-10 22:29:59 -07:00
Cameron Gutman 546843a26c Fix crash on quit confirmation prompt 2018-07-28 00:13:20 -07:00
Cameron Gutman d03d260535 Add status bar and navigation bar color on L+ 2018-07-27 23:48:20 -07:00
Cameron Gutman 6946e3c7a2 Just use the PC name as the app list title 2018-07-27 23:42:57 -07:00
30 changed files with 432 additions and 284 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
# ProBot No Response (https://probot.github.io/apps/no-response/)
daysUntilClose: 7
responseRequiredLabel: 'need more info'
closeComment: >
This issue has been automatically closed because there was no response to a
request for more information from the issue opener. Please leave a comment or
open a new issue if you have additional information related to this issue.
+14
View File
@@ -0,0 +1,14 @@
# ProBot Stale (https://probot.github.io/apps/stale/)
daysUntilStale: 90
daysUntilClose: 7
exemptLabels:
- accepted
- bug
- enhancement
- meta
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
closeComment: false
+1 -3
View File
@@ -1,4 +1,4 @@
# Moonlight # Moonlight Android
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. [Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
We reverse engineered the Shield streaming software and created a version that can be run on any Android device. We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
@@ -6,8 +6,6 @@ We reverse engineered the Shield streaming software and created a version that c
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device, Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
whether in your own home or over the internet. whether in your own home or over the internet.
[Moonlight-pc](https://github.com/moonlight-stream/moonlight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/moonlight-stream/moonlight-ios) and [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) are also in development.
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide. Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
## Features ## Features
+3 -3
View File
@@ -1,15 +1,15 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
buildToolsVersion '28.0.1' buildToolsVersion '28.0.3'
compileSdkVersion 28 compileSdkVersion 28
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionName "5.9" versionName "5.10"
versionCode = 168 versionCode = 175
} }
flavorDimensions "root" flavorDimensions "root"
+14 -4
View File
@@ -28,6 +28,7 @@ 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;
@@ -257,10 +258,9 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
String computerName = getIntent().getStringExtra(NAME_EXTRA); String computerName = getIntent().getStringExtra(NAME_EXTRA);
String labelText = getResources().getString(R.string.title_applist)+" "+computerName;
TextView label = findViewById(R.id.appListText); TextView label = findViewById(R.id.appListText);
setTitle(labelText); setTitle(computerName);
label.setText(labelText); label.setText(computerName);
// Add a launcher shortcut for this PC (forced, since this is user interaction) // Add a launcher shortcut for this PC (forced, since this is user interaction)
shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true); shortcutHelper.createAppViewShortcut(uuidString, computerName, uuidString, true);
@@ -344,7 +344,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details)); menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut)); // Only add an option to create shortcut if box art is loaded
// and when we're in grid-mode (not list-mode).
ImageView appImageView = info.targetView.findViewById(R.id.grid_image);
if (appImageView != null) {
// We have a grid ImageView, so we must be in grid-mode
BitmapDrawable drawable = (BitmapDrawable)appImageView.getDrawable();
if (drawable != null && drawable.getBitmap() != null) {
// We have a bitmap loaded too
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
}
}
} }
} }
+57 -4
View File
@@ -105,6 +105,8 @@ 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 boolean gotBackPointerEvent = false;
private boolean syntheticBackDown = false;
private ShortcutHelper shortcutHelper; private ShortcutHelper shortcutHelper;
@@ -838,9 +840,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// the right mouse button is held down, so we ignore those. // the right mouse button is held down, so we ignore those.
if ((event.getSource() == InputDevice.SOURCE_MOUSE || if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) && event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
event.getRepeatCount() == 0) { // It appears this may get turned into a right-click pointer event
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT); // if we don't return true to indicate that we handled it on
// some devices. https://github.com/moonlight-stream/moonlight-android/issues/634
if (event.getRepeatCount() == 0 && !gotBackPointerEvent) {
syntheticBackDown = true;
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
return true; return true;
} }
@@ -892,7 +899,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if ((event.getSource() == InputDevice.SOURCE_MOUSE || if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) && event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
event.getKeyCode() == KeyEvent.KEYCODE_BACK) { event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT); // It appears this may get turned into a right-click pointer event
// if we don't return true to indicate that we handled it on
// some devices. https://github.com/moonlight-stream/moonlight-android/issues/634
if (!gotBackPointerEvent || syntheticBackDown) {
// We need to raise the button if gotBackPointerEvent is true
// in the case where it transitioned to true after we already
// sent the right click down event.
syntheticBackDown = false;
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
return true; return true;
} }
@@ -999,6 +1015,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
else { else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT); conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
} }
// Don't use the KEYCODE_BACK hack (which interferes with mice
// with actual back buttons) since we're getting right clicks
// using this callback.
gotBackPointerEvent = true;
} }
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) { if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
@@ -1010,6 +1031,32 @@ public class Game extends Activity implements SurfaceHolder.Callback,
} }
} }
// HACK: Disable mouse back button press on Xiaomi due to reported
// issues with right clicks triggering it.
if (!("Xiaomi".equalsIgnoreCase(Build.MANUFACTURER))) {
if ((changedButtons & MotionEvent.BUTTON_BACK) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_BACK) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_X1);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_X1);
}
// Don't use the KEYCODE_BACK hack. That will cause this
// button press to trigger a right-click.
gotBackPointerEvent = true;
}
}
if ((changedButtons & MotionEvent.BUTTON_FORWARD) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_FORWARD) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_X2);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_X2);
}
}
// Get relative axis values if we can // Get relative axis values if we can
if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) { if (inputCaptureProvider.eventHasRelativeMouseAxes(event)) {
// Send the deltas straight from the motion event // Send the deltas straight from the motion event
@@ -1360,6 +1407,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
case EvdevListener.BUTTON_RIGHT: case EvdevListener.BUTTON_RIGHT:
buttonIndex = MouseButtonPacket.BUTTON_RIGHT; buttonIndex = MouseButtonPacket.BUTTON_RIGHT;
break; break;
case EvdevListener.BUTTON_X1:
buttonIndex = MouseButtonPacket.BUTTON_X1;
break;
case EvdevListener.BUTTON_X2:
buttonIndex = MouseButtonPacket.BUTTON_X2;
break;
default: default:
LimeLog.warning("Unhandled button: "+buttonId); LimeLog.warning("Unhandled button: "+buttonId);
return; return;
+18 -13
View File
@@ -313,8 +313,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
// Inflate the context menu // Inflate the context menu
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE || if (computer.details.state == ComputerDetails.State.OFFLINE ||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) { computer.details.state == ComputerDetails.State.UNKNOWN) {
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol)); menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc)); menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
} }
@@ -346,7 +346,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void doPair(final ComputerDetails computer) { private void doPair(final ComputerDetails computer) {
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
@@ -478,7 +478,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void doUnpair(final ComputerDetails computer) { private void doUnpair(final ComputerDetails computer) {
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
@@ -530,7 +530,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void doAppList(ComputerDetails computer) { private void doAppList(ComputerDetails computer) {
if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
@@ -567,12 +567,17 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
LimeLog.info("Ignoring delete PC request from monkey"); LimeLog.info("Ignoring delete PC request from monkey");
return true; return true;
} }
if (managerBinder == null) { UiHelper.displayDeletePcConfirmationDialog(this, computer.details, new Runnable() {
Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show(); @Override
return true; public void run() {
} if (managerBinder == null) {
managerBinder.removeComputer(computer.details.name); Toast.makeText(PcView.this, getResources().getString(R.string.error_manager_not_running), Toast.LENGTH_LONG).show();
removeComputer(computer.details); return;
}
managerBinder.removeComputer(computer.details.name);
removeComputer(computer.details);
}
}, null);
return true; return true;
case APP_LIST_ID: case APP_LIST_ID:
@@ -685,8 +690,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
public void onItemClick(AdapterView<?> arg0, View arg1, int pos, public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) { long id) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN || if (computer.details.state == ComputerDetails.State.UNKNOWN ||
computer.details.reachability == ComputerDetails.Reachability.OFFLINE) { computer.details.state == ComputerDetails.State.OFFLINE) {
// Open the context menu if a PC is offline or refreshing // Open the context menu if a PC is offline or refreshing
openContextMenu(arg1); openContextMenu(arg1);
} else if (computer.details.pairState != PairState.PAIRED) { } else if (computer.details.pairState != PairState.PAIRED) {
@@ -112,11 +112,15 @@ public class ShortcutTrampoline extends Activity {
// Now start the activities // Now start the activities
startActivities(intentStack.toArray(new Intent[]{})); startActivities(intentStack.toArray(new Intent[]{}));
} else { } else {
// Create the start intent immediately, so we can safely unbind the managerBinder
// below before we return.
final Intent startIntent = ServerHelper.createStartIntent(ShortcutTrampoline.this,
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder);
UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() { UiHelper.displayQuitConfirmationDialog(ShortcutTrampoline.this, new Runnable() {
@Override @Override
public void run() { public void run() {
intentStack.add(ServerHelper.createStartIntent(ShortcutTrampoline.this, intentStack.add(startIntent);
new NvApp("app", Integer.parseInt(appIdString), false), details, managerBinder));
// Close this activity // Close this activity
finish(); finish();
@@ -4,6 +4,8 @@ public interface EvdevListener {
int BUTTON_LEFT = 1; int BUTTON_LEFT = 1;
int BUTTON_MIDDLE = 2; int BUTTON_MIDDLE = 2;
int BUTTON_RIGHT = 3; int BUTTON_RIGHT = 3;
int BUTTON_X1 = 4;
int BUTTON_X2 = 5;
void mouseMove(int deltaX, int deltaY); void mouseMove(int deltaX, int deltaY);
void mouseButtonEvent(int buttonId, boolean down); void mouseButtonEvent(int buttonId, boolean down);
@@ -1,7 +1,5 @@
package com.limelight.computers; package com.limelight.computers;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -17,15 +15,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
public class ComputerDatabaseManager { public class ComputerDatabaseManager {
private static final String COMPUTER_DB_NAME = "computers.db"; private static final String COMPUTER_DB_NAME = "computers2.db";
private static final String COMPUTER_TABLE_NAME = "Computers"; private static final String COMPUTER_TABLE_NAME = "Computers";
private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
private static final String COMPUTER_UUID_COLUMN_NAME = "UUID"; private static final String COMPUTER_UUID_COLUMN_NAME = "UUID";
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp"; private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp"; private static final String LOCAL_ADDRESS_COLUMN_NAME = "LocalAddress";
private static final String MAC_COLUMN_NAME = "Mac"; private static final String REMOTE_ADDRESS_COLUMN_NAME = "RemoteAddress";
private static final String MANUAL_ADDRESS_COLUMN_NAME = "ManualAddress";
private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__"; private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress";
private SQLiteDatabase computerDb; private SQLiteDatabase computerDb;
@@ -38,20 +35,27 @@ public class ComputerDatabaseManager {
c.deleteDatabase(COMPUTER_DB_NAME); c.deleteDatabase(COMPUTER_DB_NAME);
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
} }
initializeDb(); initializeDb(c);
} }
public void close() { public void close() {
computerDb.close(); computerDb.close();
} }
private void initializeDb() { private void initializeDb(Context c) {
// Create tables if they aren't already there // Create tables if they aren't already there
computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," + computerDb.execSQL(String.format((Locale)null,
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)", "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT)",
COMPUTER_TABLE_NAME, COMPUTER_TABLE_NAME,
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME,
REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME)); LOCAL_ADDRESS_COLUMN_NAME, REMOTE_ADDRESS_COLUMN_NAME, MANUAL_ADDRESS_COLUMN_NAME,
MAC_ADDRESS_COLUMN_NAME));
// Move all computers from the old DB (if any) to the new one
List<ComputerDetails> oldComputers = LegacyDatabaseReader.migrateAllComputers(c);
for (ComputerDetails computer : oldComputers) {
updateComputer(computer);
}
} }
public void deleteComputer(String name) { public void deleteComputer(String name) {
@@ -60,20 +64,19 @@ public class ComputerDatabaseManager {
public boolean updateComputer(ComputerDetails details) { public boolean updateComputer(ComputerDetails details) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
values.put(LOCAL_IP_COLUMN_NAME, ADDRESS_PREFIX+details.localAddress); values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(REMOTE_IP_COLUMN_NAME, ADDRESS_PREFIX+details.remoteAddress); values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress);
values.put(MAC_COLUMN_NAME, details.macAddress); values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress);
values.put(MANUAL_ADDRESS_COLUMN_NAME, details.manualAddress);
values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress);
return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
private ComputerDetails getComputerFromCursor(Cursor c) { private ComputerDetails getComputerFromCursor(Cursor c) {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
details.name = c.getString(0); String uuidStr = c.getString(0);
String uuidStr = c.getString(1);
try { try {
details.uuid = UUID.fromString(uuidStr); details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -81,43 +84,14 @@ public class ComputerDatabaseManager {
LimeLog.severe("DB: Corrupted UUID for "+details.name); LimeLog.severe("DB: Corrupted UUID for "+details.name);
} }
// An earlier schema defined addresses as byte blobs. We'll details.name = c.getString(1);
// gracefully migrate those to strings so we can store DNS names details.localAddress = c.getString(2);
// too. To disambiguate, we'll need to prefix them with a string details.remoteAddress = c.getString(3);
// greater than the allowable IP address length. details.manualAddress = c.getString(4);
try { details.macAddress = c.getString(5);
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
LimeLog.warning("DB: Legacy local address for "+details.name);
} catch (UnknownHostException e) {
// This is probably a hostname/address with the prefix string
String stringData = c.getString(2);
if (stringData.startsWith(ADDRESS_PREFIX)) {
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
}
else {
LimeLog.severe("DB: Corrupted local address for "+details.name);
}
}
try {
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
LimeLog.warning("DB: Legacy remote address for "+details.name);
} catch (UnknownHostException e) {
// This is probably a hostname/address with the prefix string
String stringData = c.getString(3);
if (stringData.startsWith(ADDRESS_PREFIX)) {
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
}
else {
LimeLog.severe("DB: Corrupted local address for "+details.name);
}
}
details.macAddress = c.getString(4);
// This signifies we don't have dynamic state (like pair state) // This signifies we don't have dynamic state (like pair state)
details.state = ComputerDetails.State.UNKNOWN; details.state = ComputerDetails.State.UNKNOWN;
details.reachability = ComputerDetails.Reachability.UNKNOWN;
return details; return details;
} }
@@ -128,13 +102,11 @@ public class ComputerDatabaseManager {
while (c.moveToNext()) { while (c.moveToNext()) {
ComputerDetails details = getComputerFromCursor(c); ComputerDetails details = getComputerFromCursor(c);
// If a field is corrupt or missing, skip the database entry // If a critical field is corrupt or missing, skip the database entry
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null || if (details.uuid == null) {
details.macAddress == null) {
continue; continue;
} }
computerList.add(details); computerList.add(details);
} }
@@ -143,8 +115,8 @@ public class ComputerDatabaseManager {
return computerList; return computerList;
} }
public ComputerDetails getComputerByName(String name) { public ComputerDetails getComputerByUUID(UUID uuid) {
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null); Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null);
if (!c.moveToFirst()) { if (!c.moveToFirst()) {
// No matching computer // No matching computer
c.close(); c.close();
@@ -154,9 +126,8 @@ public class ComputerDatabaseManager {
ComputerDetails details = getComputerFromCursor(c); ComputerDetails details = getComputerFromCursor(c);
c.close(); c.close();
// If a field is corrupt or missing, delete the database entry // If a critical field is corrupt or missing, delete the database entry
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null || if (details.uuid == null) {
details.macAddress == null) {
deleteComputer(details.name); deleteComputer(details.name);
return null; return null;
} }
@@ -3,10 +3,9 @@ package com.limelight.computers;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.StringReader; import java.io.StringReader;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; 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.UUID;
@@ -15,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.limelight.LimeLog; import com.limelight.LimeLog;
import com.limelight.binding.PlatformBinding; import com.limelight.binding.PlatformBinding;
import com.limelight.discovery.DiscoveryService; import com.limelight.discovery.DiscoveryService;
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;
@@ -95,7 +95,6 @@ public class ComputerManagerService extends Service {
} }
details.state = ComputerDetails.State.OFFLINE; details.state = ComputerDetails.State.OFFLINE;
details.reachability = ComputerDetails.Reachability.OFFLINE;
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
@@ -109,7 +108,7 @@ public class ComputerManagerService extends Service {
if (!newPc) { if (!newPc) {
// Check if it's in the database because it could have been // Check if it's in the database because it could have been
// removed after this was issued // removed after this was issued
if (dbManager.getComputerByName(details.name) == null) { if (dbManager.getComputerByUUID(details.uuid) == null) {
// It's gone // It's gone
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
return false; return false;
@@ -156,7 +155,7 @@ public class ComputerManagerService extends Service {
} }
} }
}; };
t.setName("Polling thread for " + tuple.computer.localAddress); t.setName("Polling thread for " + tuple.computer.name);
return t; return t;
} }
@@ -177,7 +176,6 @@ public class ComputerManagerService extends Service {
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) { if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
LimeLog.info("Timing out polled state for "+tuple.computer.name); LimeLog.info("Timing out polled state for "+tuple.computer.name);
tuple.computer.state = ComputerDetails.State.UNKNOWN; tuple.computer.state = ComputerDetails.State.UNKNOWN;
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
} }
// Report this computer initially // Report this computer initially
@@ -253,7 +251,6 @@ public class ComputerManagerService extends Service {
// from wiping this change out // from wiping this change out
synchronized (tuple.networkLock) { synchronized (tuple.networkLock) {
tuple.computer.state = ComputerDetails.State.UNKNOWN; tuple.computer.state = ComputerDetails.State.UNKNOWN;
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
} }
} }
} }
@@ -312,17 +309,8 @@ public class ComputerManagerService extends Service {
for (PollingTuple tuple : pollingTuples) { for (PollingTuple tuple : pollingTuples) {
// Check if this is the same computer // Check if this is the same computer
if (tuple.computer.uuid.equals(details.uuid)) { if (tuple.computer.uuid.equals(details.uuid)) {
if (manuallyAdded) { // Update the saved computer with potentially new details
// Update details anyway in case this machine has been re-added by IP tuple.computer.update(details);
// after not being reachable by our existing information
tuple.computer.localAddress = details.localAddress;
tuple.computer.remoteAddress = details.remoteAddress;
}
else {
// This indicates that mDNS discovered this address, so we
// should only apply the local address.
tuple.computer.localAddress = details.localAddress;
}
// Start a polling thread if polling is active // Start a polling thread if polling is active
if (pollingActive && tuple.thread == null) { if (pollingActive && tuple.thread == null) {
@@ -350,8 +338,20 @@ public class ComputerManagerService extends Service {
public boolean addComputerBlocking(String addr, boolean manuallyAdded) { public boolean addComputerBlocking(String addr, boolean manuallyAdded) {
// Setup a placeholder // Setup a placeholder
ComputerDetails fakeDetails = new ComputerDetails(); ComputerDetails fakeDetails = new ComputerDetails();
fakeDetails.localAddress = addr;
fakeDetails.remoteAddress = addr; if (manuallyAdded) {
// Add PC UI
fakeDetails.manualAddress = addr;
}
else {
// mDNS
fakeDetails.localAddress = addr;
// Since we're on the same network, we can use STUN to find
// our WAN address, which is also very likely the WAN address
// of the PC. We can use this later to connect remotely.
fakeDetails.remoteAddress = NvConnection.findExternalAddressForMdns();
}
// Block while we try to fill the details // Block while we try to fill the details
try { try {
@@ -438,6 +438,9 @@ public class ComputerManagerService extends Service {
return null; return null;
} }
// Set the new active address
newDetails.activeAddress = address;
return newDetails; return newDetails;
} catch (Exception e) { } catch (Exception e) {
return null; return null;
@@ -447,6 +450,11 @@ public class ComputerManagerService extends Service {
// Just try to establish a TCP connection to speculatively detect a running // Just try to establish a TCP connection to speculatively detect a running
// GFE server // GFE server
private boolean fastPollIp(String address) { private boolean fastPollIp(String address) {
if (address == null) {
// Don't bother if our address is null
return false;
}
Socket s = new Socket(); Socket s = new Socket();
try { try {
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT); s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
@@ -475,12 +483,14 @@ public class ComputerManagerService extends Service {
t.start(); t.start();
} }
private ComputerDetails.Reachability fastPollPc(final String localAddress, final String remoteAddress) throws InterruptedException { private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress) throws InterruptedException {
final boolean[] remoteInfo = new boolean[2]; final boolean[] remoteInfo = new boolean[2];
final boolean[] localInfo = new boolean[2]; final boolean[] localInfo = new boolean[2];
final boolean[] manualInfo = new boolean[2];
startFastPollThread(localAddress, localInfo); startFastPollThread(localAddress, localInfo);
startFastPollThread(remoteAddress, remoteInfo); startFastPollThread(remoteAddress, remoteInfo);
startFastPollThread(manualAddress, manualInfo);
// Check local first // Check local first
synchronized (localInfo) { synchronized (localInfo) {
@@ -489,174 +499,78 @@ public class ComputerManagerService extends Service {
} }
if (localInfo[1]) { if (localInfo[1]) {
return ComputerDetails.Reachability.LOCAL; return localAddress;
} }
} }
// Now remote // Now manual
synchronized (manualInfo) {
while (!manualInfo[0]) {
manualInfo.wait(500);
}
if (manualInfo[1]) {
return manualAddress;
}
}
// And finally, remote
synchronized (remoteInfo) { synchronized (remoteInfo) {
while (!remoteInfo[0]) { while (!remoteInfo[0]) {
remoteInfo.wait(500); remoteInfo.wait(500);
} }
if (remoteInfo[1]) { if (remoteInfo[1]) {
return ComputerDetails.Reachability.REMOTE; return remoteAddress;
} }
} }
return ComputerDetails.Reachability.OFFLINE; return null;
}
private static boolean isAddressLikelyLocal(String str) {
try {
// This will tend to be wrong for IPv6 but falling back to
// remote will be fine in that case. For IPv4, it should be
// pretty accurate due to NAT prevalence.
InetAddress addr = InetAddress.getByName(str);
return addr.isSiteLocalAddress() || addr.isLinkLocalAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
return false;
}
}
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails;
ComputerDetails.Reachability reachability;
if (details.localAddress.equals(details.remoteAddress)) {
reachability = isAddressLikelyLocal(details.localAddress) ?
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
}
else {
// Do a TCP-level connection to the HTTP server to see if it's listening
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +")");
reachability = fastPollPc(details.localAddress, details.remoteAddress);
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
// If no connection could be established to either IP address, there's nothing we can do
if (reachability == ComputerDetails.Reachability.OFFLINE) {
return null;
}
}
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
if (localFirst) {
polledDetails = tryPollIp(details, details.localAddress);
}
else {
polledDetails = tryPollIp(details, details.remoteAddress);
}
String reachableAddr = null;
if (polledDetails == null && !details.localAddress.equals(details.remoteAddress)) {
// Failed, so let's try the fallback
if (!localFirst) {
polledDetails = tryPollIp(details, details.localAddress);
}
else {
polledDetails = tryPollIp(details, details.remoteAddress);
}
if (polledDetails != null) {
// The fallback poll worked
reachableAddr = !localFirst ? details.localAddress : details.remoteAddress;
}
}
else if (polledDetails != null) {
reachableAddr = localFirst ? details.localAddress : details.remoteAddress;
}
if (reachableAddr == null) {
return null;
}
// If both addresses are the same, guess whether we're local based on
// IP address heuristics.
if (reachableAddr.equals(polledDetails.localAddress) &&
reachableAddr.equals(polledDetails.remoteAddress)) {
polledDetails.reachability = isAddressLikelyLocal(reachableAddr) ?
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.remoteAddress.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.localAddress.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
}
else {
polledDetails.reachability = ComputerDetails.Reachability.UNKNOWN;
}
return new ReachabilityTuple(polledDetails, reachableAddr);
} }
private boolean pollComputer(ComputerDetails details) throws InterruptedException { private boolean pollComputer(ComputerDetails details) throws InterruptedException {
ReachabilityTuple initialReachTuple = pollForReachability(details); ComputerDetails polledDetails;
if (initialReachTuple == null) {
// Do a TCP-level connection to the HTTP server to see if it's listening.
// Do not write this address to details.activeAddress because:
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress +")");
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress);
LimeLog.info("Fast poll for "+details.name+" returned active address: "+details.activeAddress);
// If no connection could be established to either IP address, there's nothing we can do
if (candidateAddress == null) {
return false; return false;
} }
if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) { // Try using the active address from fast-poll
// Neither IP address reported in the serverinfo response was the one we used. polledDetails = tryPollIp(details, candidateAddress);
// Poll again to see if we can contact this machine on either of its reported addresses. if (polledDetails == null) {
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer); // If that failed, try all unique addresses except what we've
if (confirmationReachTuple == null) { // already tried
// Neither of those seem to work, so we'll hold onto the address that did work HashSet<String> uniqueAddresses = new HashSet<>();
initialReachTuple.computer.localAddress = initialReachTuple.reachableAddress; uniqueAddresses.add(details.localAddress);
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL; uniqueAddresses.add(details.remoteAddress);
} uniqueAddresses.add(details.manualAddress);
else { for (String addr : uniqueAddresses) {
// We got it on one of the returned addresses; replace the original reach tuple if (addr == null || addr.equals(candidateAddress)) {
// with the new one continue;
initialReachTuple = confirmationReachTuple; }
polledDetails = tryPollIp(details, addr);
if (polledDetails != null) {
break;
}
} }
} }
// Save some details about the old state of the PC that we may wish if (polledDetails != null) {
// to restore later. details.update(polledDetails);
String savedMacAddress = details.macAddress; return true;
String savedLocalAddress = details.localAddress;
String savedRemoteAddress = details.remoteAddress;
// If we got here, it's reachable
details.update(initialReachTuple.computer);
// If the new MAC address is empty, restore the old one (workaround for GFE bug)
if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) {
LimeLog.info("MAC address was empty; using existing value: "+savedMacAddress);
details.macAddress = savedMacAddress;
} }
else {
// We never want to lose IP addresses by polling server info. If we get a poll back return false;
// where localAddress == remoteAddress but savedLocalAddress != savedRemoteAddress,
// then we've lost an address in the polling and we should restore the one that's missing.
if (details.localAddress.equals(details.remoteAddress) &&
!savedLocalAddress.equals(savedRemoteAddress)) {
if (details.localAddress.equals(savedLocalAddress)) {
// Local addresses are identical, so put the old remote address back
details.remoteAddress = savedRemoteAddress;
}
else if (details.remoteAddress.equals(savedRemoteAddress)) {
// Remote addresses are identical, so put the old local address back
details.localAddress = savedLocalAddress;
}
else {
// Neither IP address match. Let's restore the remote address to be safe.
details.remoteAddress = savedRemoteAddress;
}
// Now update the reachability so the correct address is used
if (details.localAddress.equals(initialReachTuple.reachableAddress)) {
details.reachability = ComputerDetails.Reachability.LOCAL;
}
else {
details.reachability = ComputerDetails.Reachability.REMOTE;
}
} }
return true;
} }
@Override @Override
@@ -841,7 +755,7 @@ public class ComputerManagerService extends Service {
} while (waitPollingDelay()); } while (waitPollingDelay());
} }
}; };
thread.setName("App list polling thread for " + computer.localAddress); thread.setName("App list polling thread for " + computer.name);
thread.start(); thread.start();
} }
@@ -0,0 +1,113 @@
package com.limelight.computers;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import com.limelight.LimeLog;
import com.limelight.nvstream.http.ComputerDetails;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class LegacyDatabaseReader {
private static final String COMPUTER_DB_NAME = "computers.db";
private static final String COMPUTER_TABLE_NAME = "Computers";
private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__";
private static ComputerDetails getComputerFromCursor(Cursor c) {
ComputerDetails details = new ComputerDetails();
details.name = c.getString(0);
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
// gracefully migrate those to strings so we can store DNS names
// too. To disambiguate, we'll need to prefix them with a string
// greater than the allowable IP address length.
try {
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
LimeLog.warning("DB: Legacy local address for " + details.name);
} catch (UnknownHostException e) {
// This is probably a hostname/address with the prefix string
String stringData = c.getString(2);
if (stringData.startsWith(ADDRESS_PREFIX)) {
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
} else {
LimeLog.severe("DB: Corrupted local address for " + details.name);
}
}
try {
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
LimeLog.warning("DB: Legacy remote address for " + details.name);
} catch (UnknownHostException e) {
// This is probably a hostname/address with the prefix string
String stringData = c.getString(3);
if (stringData.startsWith(ADDRESS_PREFIX)) {
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
} else {
LimeLog.severe("DB: Corrupted remote address for " + details.name);
}
}
// On older versions of Moonlight, this is typically where manual addresses got stored,
// so let's initialize it just to be safe.
details.manualAddress = details.remoteAddress;
details.macAddress = c.getString(4);
// This signifies we don't have dynamic state (like pair state)
details.state = ComputerDetails.State.UNKNOWN;
return details;
}
private static List<ComputerDetails> getAllComputers(SQLiteDatabase db) {
Cursor c = db.rawQuery("SELECT * FROM " + COMPUTER_TABLE_NAME, null);
LinkedList<ComputerDetails> computerList = new LinkedList<>();
while (c.moveToNext()) {
ComputerDetails details = getComputerFromCursor(c);
// If a critical field is corrupt or missing, skip the database entry
if (details.uuid == null) {
continue;
}
computerList.add(details);
}
c.close();
return computerList;
}
public static List<ComputerDetails> migrateAllComputers(Context c) {
SQLiteDatabase computerDb = null;
try {
// Open the existing database
computerDb = SQLiteDatabase.openDatabase(c.getDatabasePath(COMPUTER_DB_NAME).getPath(), null, SQLiteDatabase.OPEN_READONLY);
return getAllComputers(computerDb);
} catch (SQLiteException e) {
return new LinkedList<ComputerDetails>();
} finally {
// Close and delete the old DB
if (computerDb != null) {
computerDb.close();
}
c.deleteDatabase(COMPUTER_DB_NAME);
}
}
}
@@ -46,7 +46,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
imgView.setAlpha(0.4f); imgView.setAlpha(0.4f);
} }
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) { if (obj.details.state == ComputerDetails.State.UNKNOWN) {
prgView.setVisibility(View.VISIBLE); prgView.setVisibility(View.VISIBLE);
} }
else { else {
@@ -62,9 +62,8 @@ public class StreamView extends SurfaceView {
@Override @Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) { public boolean onKeyPreIme(int keyCode, KeyEvent event) {
// This callbacks allows us to override dumb IME behavior like when // This callbacks allows us to override dumb IME behavior like when
// Samsung's default keyboard consumes Shift+Space. We'll process // Samsung's default keyboard consumes Shift+Space.
// the input event directly if any modifier keys are down. if (inputCallbacks != null) {
if (inputCallbacks != null && event.getModifiers() != 0) {
if (event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (inputCallbacks.handleKeyDown(event)) { if (inputCallbacks.handleKeyDown(event)) {
return true; return true;
@@ -18,8 +18,7 @@ import java.net.UnknownHostException;
public class ServerHelper { public class ServerHelper {
public static String getCurrentAddressFromComputer(ComputerDetails computer) { public static String getCurrentAddressFromComputer(ComputerDetails computer) {
return computer.reachability == ComputerDetails.Reachability.LOCAL ? return computer.activeAddress;
computer.localAddress : computer.remoteAddress;
} }
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer, public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
@@ -30,8 +29,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_STREAMING_REMOTE, intent.putExtra(Game.EXTRA_STREAMING_REMOTE, getCurrentAddressFromComputer(computer).equals(computer.remoteAddress));
computer.reachability != ComputerDetails.Reachability.LOCAL);
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString()); intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
intent.putExtra(Game.EXTRA_PC_NAME, computer.name); intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
return intent; return intent;
@@ -12,6 +12,7 @@ import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import com.limelight.R; import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.preferences.PreferenceConfiguration; import com.limelight.preferences.PreferenceConfiguration;
import java.util.Locale; import java.util.Locale;
@@ -135,4 +136,32 @@ public class UiHelper {
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener) .setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
.show(); .show();
} }
public static void displayDeletePcConfirmationDialog(Activity parent, ComputerDetails computer, final Runnable onYes, final Runnable onNo) {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
if (onYes != null) {
onYes.run();
}
break;
case DialogInterface.BUTTON_NEGATIVE:
if (onNo != null) {
onNo.run();
}
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(parent);
builder.setMessage(parent.getResources().getString(R.string.delete_pc_msg))
.setTitle(computer.name)
.setPositiveButton(parent.getResources().getString(R.string.yes), dialogClickListener)
.setNegativeButton(parent.getResources().getString(R.string.no), dialogClickListener)
.show();
}
} }
-1
View File
@@ -61,7 +61,6 @@
<string name="lost_connection">Conexión perdida</string> <string name="lost_connection">Conexión perdida</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Aplicaciones en</string>
<string name="applist_menu_resume">Reanudar sesión</string> <string name="applist_menu_resume">Reanudar sesión</string>
<string name="applist_menu_quit">Cerrar sesión</string> <string name="applist_menu_quit">Cerrar sesión</string>
<string name="applist_menu_quit_and_start">Cerrar juego actual e iniciar</string> <string name="applist_menu_quit_and_start">Cerrar juego actual e iniciar</string>
+31 -3
View File
@@ -3,6 +3,9 @@
<!-- 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>
<string name="scut_pc_not_found">PC non trouvé</string>
<string name="scut_invalid_uuid">Le PC fourni n\'est pas valide</string>
<string name="scut_invalid_app_id">L\'application fournie n\'est pas valide</string>
<!-- Help strings --> <!-- Help strings -->
<string name="help_loading_title">Visionneuse d\'aide</string> <string name="help_loading_title">Visionneuse d\'aide</string>
@@ -14,6 +17,7 @@
<string name="pcview_menu_unpair_pc">Désappairer</string> <string name="pcview_menu_unpair_pc">Désappairer</string>
<string name="pcview_menu_send_wol">Envoyer la requête Wake-On-LAN</string> <string name="pcview_menu_send_wol">Envoyer la requête Wake-On-LAN</string>
<string name="pcview_menu_delete_pc">Supprimer PC</string> <string name="pcview_menu_delete_pc">Supprimer PC</string>
<string name="pcview_menu_details">Voir les détails</string>
<!-- Pair messages --> <!-- Pair messages -->
<string name="pairing">Appariement…</string> <string name="pairing">Appariement…</string>
@@ -47,6 +51,12 @@
<string name="error_404">GFE renvoi une erreur HTTP 404. Assurez-vous que votre PC exécute un GPU pris en charge. <string name="error_404">GFE renvoi une erreur HTTP 404. Assurez-vous que votre PC exécute un GPU pris en charge.
L\'utilisation d\'un logiciel de bureau à distance peut également provoquer cette erreur. Essayez de redémarrer votre machine ou de réinstaller GFE. L\'utilisation d\'un logiciel de bureau à distance peut également provoquer cette erreur. Essayez de redémarrer votre machine ou de réinstaller GFE.
</string> </string>
<string name="title_decoding_error">Le décodeur vidéo s\'est écrasé</string>
<string name="message_decoding_error">Moonlight s\'est écrasé en raison d\'une incompatibilité avec le décodeur vidéo de cet appareil. Assurez-vous que GeForce Experience soit mis à jour vers la dernière version sur votre PC. Essayez de régler les paramètres de diffusion si les plantages continuent.</string>
<string name="title_decoding_reset">Paramètres vidéo réinitialiser</string>
<string name="message_decoding_reset">Le décodeur vidéo de votre appareil continue de planter avec les paramètres de diffusion sélectionnés. Vos paramètres de diffusion ont été réinitialisés par défaut.</string>
<string name="error_usb_prohibited">L\'accès USB est interdit par votre appareil. Vérifiez vos paramètres Knox ou MDM.</string>
<string name="unable_to_pin_shortcut">Votre lanceur actuel ne permet pas de créer des raccourcis épinglés.</string>
<!-- Start application messages --> <!-- Start application messages -->
<string name="conn_establishing_title">Établissement de la connexion</string> <string name="conn_establishing_title">Établissement de la connexion</string>
@@ -63,20 +73,22 @@
<!-- General strings --> <!-- General strings -->
<string name="ip_hint">Adresse IP de GeForce PC</string> <string name="ip_hint">Adresse IP de GeForce PC</string>
<string name="searching_pc">Recherche de PC avec GameStream en cours...\n\n <string name="searching_pc">Recherche de PC avec GameStream en cours…\n\n
Assurez-vous que GameStream est activé dans les paramètres GeForce Experience SHIELD.</string> Assurez-vous que GameStream est activé dans les paramètres GeForce Experience SHIELD.</string>
<string name="yes">Oui</string> <string name="yes">Oui</string>
<string name="no">Non</string> <string name="no">Non</string>
<string name="lost_connection">Perte de connexion avec le PC</string> <string name="lost_connection">Perte de connexion avec le PC</string>
<string name="title_details">Détails</string>
<string name="help">Aide</string> <string name="help">Aide</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Applications sur</string>
<string name="applist_connect_msg">Connexion au PC…</string> <string name="applist_connect_msg">Connexion au PC…</string>
<string name="applist_menu_resume">Reprise de la session</string> <string name="applist_menu_resume">Reprise de la session</string>
<string name="applist_menu_quit">Quitter la session</string> <string name="applist_menu_quit">Quitter la session</string>
<string name="applist_menu_quit_and_start">Quitter le jeu actuel et démarrer</string> <string name="applist_menu_quit_and_start">Quitter le jeu actuel et démarrer</string>
<string name="applist_menu_cancel">Annuler</string> <string name="applist_menu_cancel">Annuler</string>
<string name="applist_menu_details">Voir les détails</string>
<string name="applist_menu_scut">Créer un raccourci</string>
<string name="applist_refresh_title">Liste des applications</string> <string name="applist_refresh_title">Liste des applications</string>
<string name="applist_refresh_msg">Actualisation des applications…</string> <string name="applist_refresh_msg">Actualisation des applications…</string>
<string name="applist_refresh_error_title">Erreur</string> <string name="applist_refresh_error_title">Erreur</string>
@@ -85,6 +97,7 @@
<string name="applist_quit_success">Fermeture avec succès</string> <string name="applist_quit_success">Fermeture avec succès</string>
<string name="applist_quit_fail">Échec de la fermeture</string> <string name="applist_quit_fail">Échec de la fermeture</string>
<string name="applist_quit_confirmation">Voulez-vous vraiment quitter l\'application en cours d\'exécution? Toutes les données non enregistrées seront perdues.</string> <string name="applist_quit_confirmation">Voulez-vous vraiment quitter l\'application en cours d\'exécution? Toutes les données non enregistrées seront perdues.</string>
<string name="applist_details_id">ID app:</string>
<!-- Add computer manually activity --> <!-- Add computer manually activity -->
<string name="title_add_pc">Ajouter un PC manuellement</string> <string name="title_add_pc">Ajouter un PC manuellement</string>
@@ -93,6 +106,7 @@
<string name="addpc_success">Ajouté avec succès de l\'ordinateur</string> <string name="addpc_success">Ajouté avec succès de l\'ordinateur</string>
<string name="addpc_unknown_host">Impossible de résoudre l\'adresse du PC. Assurez-vous que vous n\'avez pas fait une faute de frappe dans l\'adresse.</string> <string name="addpc_unknown_host">Impossible de résoudre l\'adresse du PC. Assurez-vous que vous n\'avez pas fait une faute de frappe dans l\'adresse.</string>
<string name="addpc_enter_ip">Vous devez entrer une adresse IP</string> <string name="addpc_enter_ip">Vous devez entrer une adresse IP</string>
<string name="addpc_wrong_sitelocal">Cette adresse ne semble pas correcte. Vous devez utiliser l\'adresse IP publique de votre routeur pour la diffusion en continu sur Internet..</string>
<!-- Preferences --> <!-- Preferences -->
<string name="category_basic_settings">Paramètres de base</string> <string name="category_basic_settings">Paramètres de base</string>
@@ -104,6 +118,8 @@
<string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string> <string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string>
<string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string> <string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string>
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string> <string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string>
<string name="title_checkbox_enable_pip">Activer le mode observateur dans l\'image</string>
<string name="summary_checkbox_enable_pip">Permet de visualiser le flux (sans le contrôleur) tout en multitâche</string>
<string name="category_audio_settings">Paramètres audio</string> <string name="category_audio_settings">Paramètres audio</string>
<string name="title_checkbox_51_surround">Activer son surround 5.1</string> <string name="title_checkbox_51_surround">Activer son surround 5.1</string>
@@ -116,12 +132,21 @@
<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>
<string name="summary_checkbox_xb1_driver">Active un pilote USB intégré pour les périphériques sans prise en charge du contrôleur Xbox natif.</string> <string name="summary_checkbox_xb1_driver">Active un pilote USB intégré pour les périphériques sans prise en charge du contrôleur Xbox natif.</string>
<string name="title_checkbox_usb_bind_all">Ignorer le support du contrôleur Android</string>
<string name="summary_checkbox_usb_bind_all">Force le pilote USB de Moonlight à prendre en charge tous les gamepads Xbox pris en charge</string>
<string name="title_checkbox_mouse_emulation">Emulation de la souris via le gamepad</string>
<string name="summary_checkbox_mouse_emulation">Appuyez longuement sur le bouton Start pour faire basculer la manette de jeu en mode souris.</string>
<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_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="summary_reset_osc">Rétablit la taille et la position par défaut de tous les contrôles à l\'écran</string>
<string name="dialog_title_reset_osc">Réinitialiser la mise en page</string>
<string name="dialog_text_reset_osc">Êtes-vous sûr de vouloir supprimer la disposition des commandes à l\'écran que vous avez sauvegardée?</string>
<string name="toast_reset_osc_success">Les contrôles à l\'écran sont réinitialisés</string>
<string name="category_ui_settings">Paramètres de l\'interface utilisateur</string> <string name="category_ui_settings">Paramètres de l\'interface utilisateur</string>
<string name="title_language_list">Langue</string> <string name="title_language_list">Langue</string>
@@ -139,5 +164,8 @@
<string name="category_advanced_settings">Réglages avancés</string> <string name="category_advanced_settings">Réglages avancés</string>
<string name="title_video_format">Modifier les paramètres H.265</string> <string name="title_video_format">Modifier les paramètres H.265</string>
<string name="summary_video_format">H.265 réduit les exigences de bande passante vidéo, mais requiert un périphérique très récent.</string> <string name="summary_video_format">H.265 réduit les besoins en bande passante vidéo mais nécessite un périphérique très récent</string>
<string name="title_enable_hdr">Activer le HDR (expérimental)</string>
<string name="summary_enable_hdr">Diffuser du HDR lorsque le jeu et le processeur graphique du PC le prennent en charge. HDR nécessite un GPU série GTX 1000 ou une version ultérieure.</string>
</resources> </resources>
-1
View File
@@ -76,7 +76,6 @@
<string name="help">Assistenza</string> <string name="help">Assistenza</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Applicazioni su</string>
<string name="applist_connect_msg">Connessione al PC in corso…</string> <string name="applist_connect_msg">Connessione al PC in corso…</string>
<string name="applist_menu_resume">Riprendi sessione</string> <string name="applist_menu_resume">Riprendi sessione</string>
<string name="applist_menu_quit">Chiudi sessione</string> <string name="applist_menu_quit">Chiudi sessione</string>
-1
View File
@@ -57,7 +57,6 @@
<string name="lost_connection">コンピュータとの接続が失われました</string> <string name="lost_connection">コンピュータとの接続が失われました</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">ゲーム</string>
<string name="applist_menu_resume">セッションを続ける</string> <string name="applist_menu_resume">セッションを続ける</string>
<string name="applist_menu_quit">セッションを終了する</string> <string name="applist_menu_quit">セッションを終了する</string>
<string name="applist_menu_quit_and_start">現在のゲームを終了して新しいゲームを始める</string> <string name="applist_menu_quit_and_start">現在のゲームを終了して新しいゲームを始める</string>
-1
View File
@@ -71,7 +71,6 @@
<string name="help">도움말</string> <string name="help">도움말</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">앱 사용 가능</string>
<string name="applist_connect_msg">PC에 연결중…</string> <string name="applist_connect_msg">PC에 연결중…</string>
<string name="applist_menu_resume">세션 계속</string> <string name="applist_menu_resume">세션 계속</string>
<string name="applist_menu_quit">세션 종료</string> <string name="applist_menu_quit">세션 종료</string>
-1
View File
@@ -61,7 +61,6 @@
<string name="lost_connection">Verbinding met PC verloren</string> <string name="lost_connection">Verbinding met PC verloren</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Apps op</string>
<string name="applist_menu_resume">Hervat Sessie</string> <string name="applist_menu_resume">Hervat Sessie</string>
<string name="applist_menu_quit">Stop Sessie</string> <string name="applist_menu_quit">Stop Sessie</string>
<string name="applist_menu_quit_and_start">Stop Huidige Spel en Start</string> <string name="applist_menu_quit_and_start">Stop Huidige Spel en Start</string>
-1
View File
@@ -62,7 +62,6 @@
<string name="lost_connection">Потеряно соединение с PC</string> <string name="lost_connection">Потеряно соединение с PC</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Приложения на</string>
<string name="applist_menu_resume">Возобновить сессию</string> <string name="applist_menu_resume">Возобновить сессию</string>
<string name="applist_menu_quit">Выйти из сессии</string> <string name="applist_menu_quit">Выйти из сессии</string>
<string name="applist_menu_quit_and_start">Выйти из текущей игры и запустить</string> <string name="applist_menu_quit_and_start">Выйти из текущей игры и запустить</string>
+2
View File
@@ -7,6 +7,8 @@
--> -->
<style name="AppBaseTheme" parent="android:Theme.Material"> <style name="AppBaseTheme" parent="android:Theme.Material">
<!-- API 21 theme customizations can go here. --> <!-- API 21 theme customizations can go here. -->
<item name="android:statusBarColor">#212121</item>
<item name="android:navigationBarColor">#212121</item>
</style> </style>
</resources> </resources>
@@ -71,7 +71,6 @@
<string name="help">帮助</string> <string name="help">帮助</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg"> 连接到电脑中…… </string> <string name="applist_connect_msg"> 连接到电脑中…… </string>
<string name="applist_menu_resume"> 恢复串流 </string> <string name="applist_menu_resume"> 恢复串流 </string>
<string name="applist_menu_quit"> 退出串流 </string> <string name="applist_menu_quit"> 退出串流 </string>
@@ -71,7 +71,6 @@
<string name="help">幫助</string> <string name="help">幫助</string>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg"> 連接到電腦中…… </string> <string name="applist_connect_msg"> 連接到電腦中…… </string>
<string name="applist_menu_resume"> 恢復串流 </string> <string name="applist_menu_resume"> 恢復串流 </string>
<string name="applist_menu_quit"> 退出串流 </string> <string name="applist_menu_quit"> 退出串流 </string>
+1 -1
View File
@@ -83,9 +83,9 @@
<string name="lost_connection">Lost connection to PC</string> <string name="lost_connection">Lost connection to PC</string>
<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>
<!-- AppList activity --> <!-- AppList activity -->
<string name="title_applist">Apps on</string>
<string name="applist_connect_msg">Connecting to PC…</string> <string name="applist_connect_msg">Connecting to PC…</string>
<string name="applist_menu_resume">Resume Session</string> <string name="applist_menu_resume">Resume Session</string>
<string name="applist_menu_quit">Quit Session</string> <string name="applist_menu_quit">Quit Session</string>
@@ -55,7 +55,7 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Launch evdev_reader directly via SU // Launch evdev_reader directly via SU
try { try {
su = Runtime.getRuntime().exec("su -c "+evdevReaderCmd); su = new ProcessBuilder("su", "-c", evdevReaderCmd).start();
} catch (IOException e) { } catch (IOException e) {
reportDeviceNotRooted(); reportDeviceNotRooted();
e.printStackTrace(); e.printStackTrace();
@@ -151,7 +151,15 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
break; break;
case EvdevEvent.BTN_SIDE: case EvdevEvent.BTN_SIDE:
listener.mouseButtonEvent(EvdevListener.BUTTON_X1,
event.value != 0);
break;
case EvdevEvent.BTN_EXTRA: case EvdevEvent.BTN_EXTRA:
listener.mouseButtonEvent(EvdevListener.BUTTON_X2,
event.value != 0);
break;
case EvdevEvent.BTN_FORWARD: case EvdevEvent.BTN_FORWARD:
case EvdevEvent.BTN_BACK: case EvdevEvent.BTN_BACK:
case EvdevEvent.BTN_TASK: case EvdevEvent.BTN_TASK:
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-beta04' classpath 'com.android.tools.build:gradle:3.2.1'
} }
} }