diff options
| author | Lena <lena@omega> | 2026-07-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-07-01 00:00:00 +0000 |
| commit | 30ee8be82e44d1a8250d1f6e27e71f7d5f24aec5 (patch) | |
| tree | ef45988151ad217ecf2fcc81875b43e334e3aa85 /app/src | |
| parent | c03f0464c1f834c2f4d8642cbfad322033d368b7 (diff) | |
| download | scrcpy-android-30ee8be82e44d1a8250d1f6e27e71f7d5f24aec5.tar.gz | |
app: enter the device address as one host:port field
The form asked for a host, a pairing port and a connection port as three
separate fields. Nothing on screen says which port is which, and the two
are only distinguishable if the reader already knows how adb pair works
on Android 11+.
Take the connection endpoint the way the target prints it, as a single
"ip:port" string copied off the Wireless debugging screen, and leave the
pairing dialog with just its port and code. Devices.parseAddress() does
the split and rejects anything malformed rather than guessing: an
unbracketed IPv6 literal would otherwise be split at the wrong colon.
Reported on the F-Droid submission (fdroiddata!41394).
Diffstat (limited to 'app/src')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Devices.java | 44 | ||||
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Main.java | 58 | ||||
| -rw-r--r-- | app/src/main/res/layout/main.xml | 43 | ||||
| -rw-r--r-- | app/src/main/res/values/strings.xml | 13 | ||||
| -rw-r--r-- | app/src/test/java/invalid/lena/scrcpy/DevicesTest.java | 42 |
5 files changed, 134 insertions, 66 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Devices.java b/app/src/main/java/invalid/lena/scrcpy/Devices.java index c3e5141..0ba61a3 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Devices.java +++ b/app/src/main/java/invalid/lena/scrcpy/Devices.java @@ -30,9 +30,13 @@ public final class Devices { this.port = port; } + // Bracket IPv6 literals so the port stays unambiguous. Round-trips + // through parseAddress(). @Override public String toString() { - return host + ":" + port; + return host.indexOf(':') < 0 + ? host + ":" + port + : "[" + host + "]:" + port; } @Override @@ -49,6 +53,44 @@ public final class Devices { private Devices() {} + // Parse "host:port" exactly as the target's Wireless debugging screen + // prints it. IPv6 literals must be bracketed ("[fe80::1]:5555") because + // the address itself contains colons; an unbracketed one is rejected + // rather than silently split at the wrong colon. Returns null on + // anything malformed - the only caller is a text field. + public static Device parseAddress(String s) { + if (s == null) return null; + s = s.trim(); + String host, port; + if (s.startsWith("[")) { + int end = s.indexOf(']'); + if (end < 0 || !s.startsWith("]:", end)) return null; + host = s.substring(1, end); + port = s.substring(end + 2); + } else { + int colon = s.indexOf(':'); + if (colon < 0 || colon != s.lastIndexOf(':')) return null; + host = s.substring(0, colon); + port = s.substring(colon + 1); + } + int p = parsePort(port); + if (host.isEmpty() || p < 0) return null; + return new Device(host, p); + } + + // Digits only, 1-65535. Returns -1 if it is not a usable port. + // Integer.parseInt() alone would accept "+5555" and " 5555". + public static int parsePort(String s) { + if (s == null) return -1; + s = s.trim(); + if (s.isEmpty() || s.length() > 5) return -1; + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) < '0' || s.charAt(i) > '9') return -1; + } + int p = Integer.parseInt(s); + return p >= 1 && p <= 65535 ? p : -1; + } + // Pure-java parse: returns whatever rows are well-formed; logs and // skips anything malformed instead of nuking the list. public static List<Device> parse(String json) { diff --git a/app/src/main/java/invalid/lena/scrcpy/Main.java b/app/src/main/java/invalid/lena/scrcpy/Main.java index eb8d400..28a0870 100644 --- a/app/src/main/java/invalid/lena/scrcpy/Main.java +++ b/app/src/main/java/invalid/lena/scrcpy/Main.java @@ -18,12 +18,14 @@ import java.util.List; // Pairing form + saved-device list. // -// Layout: host, pair-port, pair-code, connect-port + a single "Pair and save" -// button. After a successful pair() against the daemon, the row is appended -// to devices.json with the *connect* port (different from the pair port on -// Android 11+). Tapping a saved row launches the Mirror activity with the -// target host/port; Mirror owns its own Adb instance loaded from the same -// on-disk keypair. +// Layout: device address ("ip:port"), pair-port, pair-code + a single +// "Pair and save" button. The address is the connect endpoint printed on +// the target's Wireless debugging screen; pairing happens on a different, +// short-lived port from the pairing dialog, against the same host. After a +// successful pair() against the daemon, the row is appended to devices.json +// with the *connect* port. Tapping a saved row launches the Mirror activity +// with the target host/port; Mirror owns its own Adb instance loaded from +// the same on-disk keypair. public final class Main extends Activity { private volatile Adb adb; @@ -41,10 +43,9 @@ public final class Main extends Activity { Ui.padForInsets(findViewById(R.id.root), WindowInsets.Type.systemBars() | WindowInsets.Type.ime()); - EditText host = findViewById(R.id.host); + EditText address = findViewById(R.id.device_address); EditText pairPort = findViewById(R.id.pair_port); EditText pairCode = findViewById(R.id.pair_code); - EditText connectPort = findViewById(R.id.connect_port); pairButton = findViewById(R.id.pair); View settingsBtn = findViewById(R.id.settings); ListView devices = findViewById(R.id.devices); @@ -89,7 +90,7 @@ public final class Main extends Activity { Devices.Device d = adapter.getItem(pos); new AlertDialog.Builder(this) .setTitle(R.string.forget_device) - .setMessage(d.host + ":" + d.port) + .setMessage(d.toString()) .setPositiveButton(android.R.string.ok, (dlg, w) -> { Log.i("forget device: %s", d); try { @@ -110,41 +111,36 @@ public final class Main extends Activity { pairButton.setOnClickListener(v -> { if (adb == null) return; // still initialising - String h = host.getText().toString().trim(); - String pp = pairPort.getText().toString().trim(); - String pc = pairCode.getText().toString().trim(); - String cp = connectPort.getText().toString().trim(); - if (TextUtils.isEmpty(h) || TextUtils.isEmpty(pp) - || TextUtils.isEmpty(pc) || TextUtils.isEmpty(cp)) { - Toast.makeText(this, "fill all four fields", Toast.LENGTH_SHORT).show(); + // The saved endpoint is the address field verbatim; pairing + // reuses its host with the pairing port. + Devices.Device target = Devices.parseAddress(address.getText().toString()); + if (target == null) { + Toast.makeText(this, R.string.bad_address, Toast.LENGTH_LONG).show(); return; } - int pairP, connP; - try { - pairP = Integer.parseInt(pp); - connP = Integer.parseInt(cp); - } catch (NumberFormatException e) { - Toast.makeText(this, "ports must be numeric", Toast.LENGTH_SHORT).show(); + int pairP = Devices.parsePort(pairPort.getText().toString()); + if (pairP < 0) { + Toast.makeText(this, R.string.bad_pair_port, Toast.LENGTH_LONG).show(); return; } - if (pairP < 1 || pairP > 65535 || connP < 1 || connP > 65535) { - Toast.makeText(this, "ports must be 1-65535", Toast.LENGTH_SHORT).show(); + String pc = pairCode.getText().toString().trim(); + if (TextUtils.isEmpty(pc)) { + Toast.makeText(this, R.string.bad_pair_code, Toast.LENGTH_LONG).show(); return; } pairButton.setEnabled(false); Toast.makeText(this, R.string.pairing, Toast.LENGTH_SHORT).show(); - new Thread(() -> pairAndSave(h, pairP, pc, connP, pairButton), "pair").start(); + new Thread(() -> pairAndSave(target, pairP, pc, pairButton), "pair").start(); }); } - private void pairAndSave(String host, int pairPort, String code, int connPort, Button btn) { + private void pairAndSave(Devices.Device target, int pairPort, String code, Button btn) { try { - Log.i("pair: %s:%d", host, pairPort); - boolean ok = adb.pair(host, pairPort, code); + Log.i("pair: %s:%d", target.host, pairPort); + boolean ok = adb.pair(target.host, pairPort, code); if (!ok) throw new IllegalStateException("pair returned false"); - Log.i("pair ok host=%s pair_port=%d", host, pairPort); - Devices.Device d = new Devices.Device(host, connPort); - List<Devices.Device> updated = Devices.upsert(this, d); + Log.i("pair ok host=%s pair_port=%d", target.host, pairPort); + List<Devices.Device> updated = Devices.upsert(this, target); runOnUiThread(() -> { adapter.clear(); adapter.addAll(updated); diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 2e722b0..0b1273b 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -44,8 +44,10 @@ </LinearLayout> - <!-- Pairing form. Fields are grouped and labelled by where their - value comes from on the target's Wireless debugging screen. --> + <!-- Pairing form. Each field is one value the target already prints, + copied verbatim: the Wireless debugging screen shows the device + address as a single "ip:port" string, and the pairing dialog adds + its own port plus the code. --> <LinearLayout style="@style/Card" android:layout_width="match_parent" @@ -67,15 +69,22 @@ style="@style/FieldLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/label_host"/> + android:text="@string/label_address"/> + + <TextView + style="@style/Caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/sub_address"/> <EditText - android:id="@+id/host" + android:id="@+id/device_address" style="@style/Field" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/host_hint" - android:inputType="text" + android:layout_marginTop="@dimen/space_xs" + android:hint="@string/address_hint" + android:inputType="textUri" android:autofillHints="postalAddress"/> <TextView @@ -120,28 +129,6 @@ </LinearLayout> - <TextView - style="@style/FieldLabel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/label_connect"/> - - <TextView - style="@style/Caption" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/sub_connect"/> - - <EditText - android:id="@+id/connect_port" - style="@style/Field" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/space_xs" - android:hint="@string/connect_port_hint" - android:inputType="number" - android:autofillHints="off"/> - <Button android:id="@+id/pair" style="@style/Button.Primary" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a9a552c..d7c45ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,16 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">scrcpy</string> - <string name="host_hint">192.168.1.42</string> + <string name="address_hint">192.168.1.42:41234</string> <string name="pair_port_hint">37123</string> <string name="pair_code_hint">6 digits</string> - <string name="connect_port_hint">41234</string> <string name="pair_help">On the target: Settings > Developer options > Wireless debugging</string> - <string name="label_host">Host / IP address</string> + <string name="label_address">Device address</string> + <string name="sub_address">The “IP address & Port” on the Wireless debugging screen</string> <string name="label_pairing">Pairing port and code</string> - <string name="sub_pairing">From the “Pair device with pairing code” dialog</string> - <string name="label_connect">Connection port</string> - <string name="sub_connect">Shown on the Wireless debugging screen</string> + <string name="sub_pairing">From the “Pair device with pairing code” dialog. Same IP, different port</string> + <string name="bad_address">Enter the device address as 192.168.1.42:41234</string> + <string name="bad_pair_port">Pairing port must be a number from 1 to 65535</string> + <string name="bad_pair_code">Enter the 6-digit pairing code</string> <string name="pair_and_save">Pair and save</string> <string name="pair_a_device">Pair a device</string> <string name="saved_devices">Saved devices</string> diff --git a/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java b/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java index d1e4f02..dcd04ca 100644 --- a/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java +++ b/app/src/test/java/invalid/lena/scrcpy/DevicesTest.java @@ -1,6 +1,7 @@ package invalid.lena.scrcpy; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -53,6 +54,47 @@ public class DevicesTest { } @Test + public void parseAddressSplitsHostAndPort() { + Devices.Device d = Devices.parseAddress(" 192.168.1.42:41234 "); + assertEquals("192.168.1.42", d.host); + assertEquals(41234, d.port); + + Devices.Device v6 = Devices.parseAddress("[fe80::1]:5555"); + assertEquals("fe80::1", v6.host); + assertEquals(5555, v6.port); + // Round-trips through the display form. + assertEquals("[fe80::1]:5555", v6.toString()); + } + + @Test + public void parseAddressRejectsMalformed() { + // No port, empty host, non-numeric and out-of-range ports. + assertNull(Devices.parseAddress(null)); + assertNull(Devices.parseAddress("")); + assertNull(Devices.parseAddress("192.168.1.42")); + assertNull(Devices.parseAddress(":5555")); + assertNull(Devices.parseAddress("192.168.1.42:")); + assertNull(Devices.parseAddress("192.168.1.42:port")); + assertNull(Devices.parseAddress("192.168.1.42:0")); + assertNull(Devices.parseAddress("192.168.1.42:65536")); + // Unbracketed IPv6 would otherwise split at the wrong colon. + assertNull(Devices.parseAddress("fe80::1:5555")); + assertNull(Devices.parseAddress("[fe80::1]5555")); + } + + @Test + public void parsePortRejectsNonDigits() { + assertEquals(5555, Devices.parsePort(" 5555 ")); + assertEquals(1, Devices.parsePort("1")); + assertEquals(65535, Devices.parsePort("65535")); + assertEquals(-1, Devices.parsePort("")); + assertEquals(-1, Devices.parsePort("+5555")); + assertEquals(-1, Devices.parsePort("-1")); + assertEquals(-1, Devices.parsePort("55x5")); + assertEquals(-1, Devices.parsePort("655360")); + } + + @Test public void deviceEqualsByHostAndPort() { Devices.Device a = new Devices.Device("1.1.1.1", 5555); Devices.Device b = new Devices.Device("1.1.1.1", 5555); |