Skip to content

Commit

Permalink
feat: app commands
Browse files Browse the repository at this point in the history
  • Loading branch information
qiin committed Aug 3, 2024
1 parent ec6ae3c commit 1e8e103
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 96 deletions.
15 changes: 7 additions & 8 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,14 @@ android {
}

dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'org.jcodec:jcodec:0.2.3'
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
implementation 'com.squareup.okio:okio:1.17.5'
// 3.5.8 requires minSdk 19, uses StandardCharsets.UTF_8 internally
implementation 'org.jmdns:jmdns:3.5.7'
implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77'
implementation 'org.jcodec:jcodec:0.2.5'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'org.jmdns:jmdns:3.5.9'
implementation 'com.github.cgutman:ShieldControllerExtensions:1.0.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.github.ZeyuKeithFu:KeyboardHeaderLayout:v1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0-alpha09'
implementation 'com.google.code.gson:gson:2.11.0'
}
28 changes: 11 additions & 17 deletions app/src/main/java/com/limelight/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
import android.widget.TextView;
import android.widget.Toast;

import org.json.JSONException;

import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -92,8 +94,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Locale;
import java.util.Optional;
import java.util.Objects;


public class Game extends Activity implements SurfaceHolder.Callback,
Expand Down Expand Up @@ -192,6 +196,7 @@ public void onServiceDisconnected(ComponentName componentName) {
public static final String EXTRA_PC_NAME = "PcName";
public static final String EXTRA_APP_HDR = "HDR";
public static final String EXTRA_SERVER_CERT = "ServerCert";
public static final String EXTRA_APP_CMD = "CmdList";

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand Down Expand Up @@ -334,8 +339,12 @@ public boolean onCapturedPointer(View view, MotionEvent motionEvent) {
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
boolean appSupportsHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
String cmdList = Game.this.getIntent().getStringExtra(EXTRA_APP_CMD);

app = new NvApp(appName != null ? appName : "app", appId, appSupportsHdr);
if (cmdList != null) {
app.setCmdList(cmdList);
}
X509Certificate serverCert = null;
try {
if (derCertData != null) {
Expand Down Expand Up @@ -2810,7 +2819,7 @@ public void onUsbPermissionPromptCompleted() {

@Override
public void showGameMenu(GameInputDevice device) {
new GameMenu(this, conn, device);
new GameMenu(this, app, conn, device);
}

@Override
Expand Down Expand Up @@ -2852,21 +2861,6 @@ public void togglePerformanceOverlay() {
performanceOverlayView.setVisibility(requestedPerformanceOverlayVisibility);
}

public void imeSwitch() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> mInputMethodProperties = imm.getInputMethodList();
Optional<InputMethodInfo> hackersInput = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
hackersInput = mInputMethodProperties.stream().filter(m -> m.getId().startsWith("org.pocketworkstation.pckeyboard")).findFirst();
}
imm.showInputMethodPicker();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (!hackersInput.isPresent()) {
Toast.makeText(Game.this, "杂鱼~❤ 还不快装黑客键盘(hacker's keyboard", Toast.LENGTH_LONG).show();
}
}
}

private static byte getModifier(short key) {
switch (key) {
case KeyboardTranslator.VK_LSHIFT:
Expand Down
33 changes: 30 additions & 3 deletions app/src/main/java/com/limelight/GameMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
import android.os.Handler;
import android.widget.ArrayAdapter;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.limelight.binding.input.GameInputDevice;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.input.KeyboardPacket;
import com.limelight.utils.ServerHelper;

import org.json.JSONArray;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -39,11 +49,13 @@ public MenuOption(String label, Runnable runnable) {
}

private final Game game;
private final NvApp app;
private final NvConnection conn;
private final GameInputDevice device;

public GameMenu(Game game, NvConnection conn, GameInputDevice device) {
public GameMenu(Game game, NvApp app, NvConnection conn, GameInputDevice device) {
this.game = game;
this.app = app;
this.conn = conn;
this.device = device;

Expand Down Expand Up @@ -178,10 +190,25 @@ private void showMenu() {
options.addAll(device.getGameMenuOptions());
}

JsonArray cmdList = app.getCmdList();
if (cmdList != null) {
for (int i = 0; i < ((JsonArray) cmdList).size(); i++) {
JsonObject cmd = cmdList.get(i).getAsJsonObject();
options.add(new MenuOption(cmd.get("name").getAsString(), () ->
{
try {
conn.sendSuperCmd(cmd.get("id").getAsString());
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
}
));
}
}

options.add(new MenuOption(getString(R.string.game_menu_toggle_performance_overlay), () -> game.togglePerformanceOverlay()));
options.add(new MenuOption(getString(R.string.game_menu_send_keys), () -> showSpecialKeysMenu()));
options.add(new MenuOption(getString(R.string.game_menu_switch_ime), () -> game.imeSwitch()));
options.add(new MenuOption(getString(R.string.game_menu_disconnect), () -> game.disconnect()));
options.add(new MenuOption(getString(R.string.game_menu_disconnect), true, () -> game.disconnect()));
options.add(new MenuOption(getString(R.string.game_menu_cancel), null));

showMenuDialog("Game Menu", options.toArray(new MenuOption[options.size()]));
Expand Down
21 changes: 19 additions & 2 deletions app/src/main/java/com/limelight/nvstream/NvConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Semaphore;

import javax.crypto.KeyGenerator;
Expand Down Expand Up @@ -588,4 +586,23 @@ public void sendUtf8Text(final String text) {
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
}

public void sendSuperCmd(String cmdId) throws IOException, XmlPullParserException {
new Thread(new Runnable() {
@Override
public void run() {
NvHTTP h = null;
try {
h = new NvHTTP(context.serverAddress, context.httpsPort, uniqueId, context.serverCert, cryptoProvider);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
h.sendSuperCmd(cmdId);
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
16 changes: 16 additions & 0 deletions app/src/main/java/com/limelight/nvstream/http/NvApp.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package com.limelight.nvstream.http;

import androidx.annotation.NonNull;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.limelight.LimeLog;


public class NvApp {
private String appName = "";
private int appId;
private boolean initialized;
private boolean hdrSupported;
private JsonArray cmdList;

public NvApp() {}

Expand Down Expand Up @@ -59,12 +65,22 @@ public boolean isInitialized() {
return this.initialized;
}

public void setCmdList(String cmdList) {
this.cmdList = new Gson().fromJson(cmdList, JsonArray.class);
}

public JsonArray getCmdList() {
return this.cmdList;
}

@NonNull
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("Name: ").append(appName).append("\n");
str.append("HDR Supported: ").append(hdrSupported ? "Yes" : "Unknown").append("\n");
str.append("ID: ").append(appId).append("\n");
if (cmdList!= null) str.append("Super CMDs: ").append(cmdList.toString()).append("\n");
return str.toString();
}
}
77 changes: 11 additions & 66 deletions app/src/main/java/com/limelight/nvstream/http/NvHTTP.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Objects;
import java.util.Stack;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -403,16 +404,7 @@ private OkHttpClient performAndroidTlsHack(OkHttpClient client) {
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(new KeyManager[] { keyManager }, new TrustManager[] { trustManager }, new SecureRandom());

// TLS 1.2 is not enabled by default prior to Android 5.0, so we'll need a custom
// SSLSocketFactory in order to connect to GFE 3.20.4 which requires TLSv1.2 or later.
// We don't just always use TLSv12SocketFactory because explicitly specifying TLS versions
// prevents later TLS versions from being negotiated even if client and server otherwise
// support them.
return client.newBuilder().sslSocketFactory(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
sc.getSocketFactory() : new TLSv12SocketFactory(sc),
trustManager).build();
return client.newBuilder().sslSocketFactory(sc.getSocketFactory(), trustManager).build();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}
Expand Down Expand Up @@ -659,6 +651,12 @@ public static LinkedList<NvApp> getAppListByReader(Reader r) throws XmlPullParse
app.setAppId(xpp.getText());
} else if (currentTag.peek().equals("IsHdrSupported")) {
app.setHdrSupported(xpp.getText().equals("1"));
} else if (currentTag.peek().equals("SuperCmds")) {
String cmdListStr = xpp.getText();
LimeLog.info(cmdListStr + " appcmds");
if (!Objects.equals(cmdListStr, "null")) {
app.setCmdList(xpp.getText());
}
}
break;
}
Expand Down Expand Up @@ -821,61 +819,8 @@ public boolean pcSleep() throws IOException, XmlPullParserException {
return !getXmlString(xmlStr, "pcsleep", true).equals("0");
}

// Based on example code from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
private static class TLSv12SocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;

public TLSv12SocketFactory(SSLContext context) {
internalSSLSocketFactory = context.getSocketFactory();
}

@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket());
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}

private Socket enableTLSv12OnSocket(Socket socket) {
if (socket instanceof SSLSocket) {
// TLS 1.2 is not enabled by default prior to Android 5.0. We must enable it
// explicitly to ensure we can communicate with GFE 3.20.4 which blocks TLS 1.0.
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
}
return socket;
}
public boolean sendSuperCmd(String cmdId) throws IOException, XmlPullParserException {
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "supercmd", "cmdId=" + cmdId);
return !getXmlString(xmlStr, "supercmd", true).equals("0");
}
}
3 changes: 3 additions & 0 deletions app/src/main/java/com/limelight/utils/ServerHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetai
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid);
intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
if (app.getCmdList() != null) {
intent.putExtra(Game.EXTRA_APP_CMD, app.getCmdList().toString());
}
try {
if (computer.serverCert != null) {
intent.putExtra(Game.EXTRA_SERVER_CERT, computer.serverCert.getEncoded());
Expand Down

0 comments on commit 1e8e103

Please sign in to comment.