Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,13 @@ Default in the plan is **Svelte**. Override before running `create-tauri-app` if
- [PLAN.md](PLAN.md) — phased porting roadmap with milestones
- [`../docs/FEATURES.md`](../docs/FEATURES.md) — behavior spec (the source of truth)
- v1: `Shield-Optimizer.ps1` at repo root

## Third-party components

The Remote tab's low-latency input channel bundles the
[scrcpy](https://github.com/Genymobile/scrcpy) server
(`src-tauri/resources/scrcpy-server-v3.1`, © Genymobile, licensed under the
[Apache License 2.0](https://github.com/Genymobile/scrcpy/blob/master/LICENSE)).
It is pushed to the device's temp storage (`/data/local/tmp`) and runs with
shell privileges only while a Remote session is open; the process exits when
the session closes.
4 changes: 2 additions & 2 deletions v2/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added v2/src-tauri/resources/scrcpy-server-v3.1
Binary file not shown.
35 changes: 35 additions & 0 deletions v2/src-tauri/src/adb/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ pub trait AdbDriver: Send + Sync {
"binary capture not supported by this driver",
)))
}

/// Spawn `adb <args...>` as a long-lived child WITHOUT awaiting completion,
/// handing the caller the `Child` to own (configured `kill_on_drop(true)`).
/// Unlike `raw`/`shell`, the process is expected to keep running — for
/// resident helpers like the scrcpy control server. Default reports
/// unsupported so mocks need no extra wiring.
async fn spawn(&self, _args: &[&str]) -> AdbResult<tokio::process::Child> {
Err(AdbError::Io(std::io::Error::other(
"process spawn not supported by this driver",
)))
}
}

/// The standard subprocess-backed driver. Wraps `tokio::process::Command`.
Expand Down Expand Up @@ -242,6 +253,30 @@ impl AdbDriver for SubprocessAdb {

Ok(output.stdout)
}

async fn spawn(&self, args: &[&str]) -> AdbResult<tokio::process::Child> {
if !self.binary.exists() {
return Err(AdbError::BinaryMissing {
path: self.binary.display().to_string(),
});
}

debug!(adb = ?self.binary, ?args, "adb spawn (long-lived)");

let mut cmd = Command::new(&self.binary);
cmd.args(args).kill_on_drop(true);
// Verified on device: the device-side `app_process` aborts at startup
// (exit 134, no Java output) when the adb client's stdin is fully
// *closed*. It needs an open fd — /dev/null works. A GUI app's inherited
// stdin is unreliable, so pin it to null explicitly. stdout/stderr are
// nulled too so the resident child can never block on a full pipe.
cmd.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
super::hide_console_window(&mut cmd);

cmd.spawn().map_err(AdbError::Io)
}
}

/// Locate an adb binary by checking the standard installation locations.
Expand Down
2 changes: 2 additions & 0 deletions v2/src-tauri/src/adb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
pub mod driver;
pub mod install;
pub mod parse;
pub mod remote_input;
pub mod scan;

use tokio::process::Command;
Expand Down Expand Up @@ -38,4 +39,5 @@ pub use parse::{
parse_total_pss_by_process, parse_usage_stats, AppUsage, DisplayMode, FileEntry, RamInfo,
StorageInfo,
};
pub use remote_input::RemoteInputSession;
pub use scan::{local_subnet_prefix, scan_subnet, ScanHit};
17 changes: 16 additions & 1 deletion v2/src-tauri/src/adb/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ pub fn parse_device_list(adb_devices_output: &str) -> Vec<DeviceListEntry> {
let Some(status) = DeviceStatus::from_adb_str(status_str) else {
continue;
};
let connection = if IP_PORT.is_match(serial) {
// Network if it's an `ip:port` serial OR an mDNS wireless-debugging
// serial (Android 11+ pairs over `_adb-tls-connect._tcp` etc.; those
// never look like `ip:port` but always carry the `_tcp` service tag).
// USB serials are plain hardware ids and contain neither.
let connection = if IP_PORT.is_match(serial) || serial.contains("._tcp") {
ConnectionType::Network
} else {
ConnectionType::Usb
Expand Down Expand Up @@ -534,6 +538,17 @@ mod tests {
assert_eq!(entries[3].status, DeviceStatus::Offline);
}

#[test]
fn mdns_wireless_debugging_serial_is_network_not_usb() {
// Android 11+ wireless debugging registers over mDNS; the serial is the
// service name, not an ip:port. It must still classify as Network.
let input = "List of devices attached\n\
adb-58040DLCH005YV-jBeCEe._adb-tls-connect._tcp\tdevice\n";
let entries = parse_device_list(input);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].connection, ConnectionType::Network);
}

#[test]
fn ignores_header_and_blank_lines() {
let input = "List of devices attached\n\n\n";
Expand Down
Loading
Loading