breaking: make ADBDeviceExt dyn-compatible (#70)
* feat: make ADBDeviceExt dyn-compatible * feat: clean CLI code
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum EmuCommand {
|
||||
/// Send a SMS with given phone number and given content
|
||||
Sms {
|
||||
phone_number: String,
|
||||
content: String,
|
||||
},
|
||||
/// Rotate device screen from 90°
|
||||
Rotate,
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
mod emu;
|
||||
mod host;
|
||||
mod local;
|
||||
mod tcp;
|
||||
mod usb;
|
||||
|
||||
pub use emu::EmuCommand;
|
||||
pub use host::{HostCommand, MdnsCommand};
|
||||
pub use local::LocalCommand;
|
||||
pub use tcp::TcpCommand;
|
||||
pub use usb::{DeviceCommands, UsbCommand};
|
||||
@@ -1,61 +0,0 @@
|
||||
use std::num::ParseIntError;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::models::RebootTypeCommand;
|
||||
|
||||
fn parse_hex_id(id: &str) -> Result<u16, ParseIntError> {
|
||||
u16::from_str_radix(id, 16)
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct UsbCommand {
|
||||
/// Hexadecimal vendor id of this USB device
|
||||
#[clap(short = 'v', long = "vendor-id", value_parser=parse_hex_id, value_name="VID")]
|
||||
pub vendor_id: Option<u16>,
|
||||
/// Hexadecimal product id of this USB device
|
||||
#[clap(short = 'p', long = "product-id", value_parser=parse_hex_id, value_name="PID")]
|
||||
pub product_id: Option<u16>,
|
||||
/// Path to a custom private key to use for authentication
|
||||
#[clap(short = 'k', long = "private-key")]
|
||||
pub path_to_private_key: Option<PathBuf>,
|
||||
#[clap(subcommand)]
|
||||
pub commands: DeviceCommands,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum DeviceCommands {
|
||||
/// Spawn an interactive shell or run a list of commands on the device
|
||||
Shell { commands: Vec<String> },
|
||||
/// Pull a file from device
|
||||
Pull { source: String, destination: String },
|
||||
/// Push a file on device
|
||||
Push { filename: String, path: String },
|
||||
/// Stat a file on device
|
||||
Stat { path: String },
|
||||
/// Run an activity on device specified by the intent
|
||||
Run {
|
||||
/// The package whose activity is to be invoked
|
||||
#[clap(short = 'p', long = "package")]
|
||||
package: String,
|
||||
/// The activity to be invoked itself, Usually it is MainActivity
|
||||
#[clap(short = 'a', long = "activity")]
|
||||
activity: String,
|
||||
},
|
||||
/// Reboot the device
|
||||
Reboot {
|
||||
#[clap(subcommand)]
|
||||
reboot_type: RebootTypeCommand,
|
||||
},
|
||||
/// Install an APK on device
|
||||
Install {
|
||||
/// Path to APK file. Extension must be ".apk"
|
||||
path: PathBuf,
|
||||
},
|
||||
/// Dump framebuffer of device
|
||||
Framebuffer {
|
||||
/// Framebuffer image destination path
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
20
adb_cli/src/handlers/emulator_commands.rs
Normal file
20
adb_cli/src/handlers/emulator_commands.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use adb_client::ADBEmulatorDevice;
|
||||
|
||||
use crate::models::{EmuCommand, EmulatorCommand};
|
||||
|
||||
pub fn handle_emulator_commands(emulator_command: EmulatorCommand) -> anyhow::Result<()> {
|
||||
let mut emulator = ADBEmulatorDevice::new(emulator_command.serial, None)?;
|
||||
|
||||
match emulator_command.command {
|
||||
EmuCommand::Sms {
|
||||
phone_number,
|
||||
content,
|
||||
} => {
|
||||
emulator.send_sms(&phone_number, &content)?;
|
||||
log::info!("SMS sent to {phone_number}");
|
||||
}
|
||||
EmuCommand::Rotate => emulator.rotate()?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
78
adb_cli/src/handlers/host_commands.rs
Normal file
78
adb_cli/src/handlers/host_commands.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use adb_client::{ADBServer, DeviceShort, MDNSBackend, Result};
|
||||
|
||||
use crate::models::{HostCommand, MdnsCommand, ServerCommand};
|
||||
|
||||
pub fn handle_host_commands(server_command: ServerCommand<HostCommand>) -> Result<()> {
|
||||
let mut adb_server = ADBServer::new(server_command.address);
|
||||
|
||||
match server_command.command {
|
||||
HostCommand::Version => {
|
||||
let version = adb_server.version()?;
|
||||
log::info!("Android Debug Bridge version {}", version);
|
||||
log::info!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
HostCommand::Kill => {
|
||||
adb_server.kill()?;
|
||||
}
|
||||
HostCommand::Devices { long } => {
|
||||
if long {
|
||||
log::info!("List of devices attached (extended)");
|
||||
for device in adb_server.devices_long()? {
|
||||
log::info!("{}", device);
|
||||
}
|
||||
} else {
|
||||
log::info!("List of devices attached");
|
||||
for device in adb_server.devices()? {
|
||||
log::info!("{}", device);
|
||||
}
|
||||
}
|
||||
}
|
||||
HostCommand::TrackDevices => {
|
||||
let callback = |device: DeviceShort| {
|
||||
log::info!("{}", device);
|
||||
Ok(())
|
||||
};
|
||||
log::info!("Live list of devices attached");
|
||||
adb_server.track_devices(callback)?;
|
||||
}
|
||||
HostCommand::Pair { address, code } => {
|
||||
adb_server.pair(address, code)?;
|
||||
log::info!("Paired device {address}");
|
||||
}
|
||||
HostCommand::Connect { address } => {
|
||||
adb_server.connect_device(address)?;
|
||||
log::info!("Connected to {address}");
|
||||
}
|
||||
HostCommand::Disconnect { address } => {
|
||||
adb_server.disconnect_device(address)?;
|
||||
log::info!("Disconnected {address}");
|
||||
}
|
||||
HostCommand::Mdns { subcommand } => match subcommand {
|
||||
MdnsCommand::Check => {
|
||||
let check = adb_server.mdns_check()?;
|
||||
let server_status = adb_server.server_status()?;
|
||||
match server_status.mdns_backend {
|
||||
MDNSBackend::Unknown => log::info!("unknown mdns backend..."),
|
||||
MDNSBackend::Bonjour => match check {
|
||||
true => log::info!("mdns daemon version [Bonjour]"),
|
||||
false => log::info!("ERROR: mdns daemon unavailable"),
|
||||
},
|
||||
MDNSBackend::OpenScreen => {
|
||||
log::info!("mdns daemon version [Openscreen discovery 0.0.0]")
|
||||
}
|
||||
}
|
||||
}
|
||||
MdnsCommand::Services => {
|
||||
log::info!("List of discovered mdns services");
|
||||
for service in adb_server.mdns_services()? {
|
||||
log::info!("{}", service);
|
||||
}
|
||||
}
|
||||
},
|
||||
HostCommand::ServerStatus => {
|
||||
log::info!("{}", adb_server.server_status()?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
35
adb_cli/src/handlers/local_commands.rs
Normal file
35
adb_cli/src/handlers/local_commands.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use adb_client::ADBServerDevice;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use crate::models::LocalDeviceCommand;
|
||||
|
||||
pub fn handle_local_commands(
|
||||
mut device: ADBServerDevice,
|
||||
local_device_commands: LocalDeviceCommand,
|
||||
) -> Result<()> {
|
||||
match local_device_commands {
|
||||
LocalDeviceCommand::HostFeatures => {
|
||||
let features = device
|
||||
.host_features()?
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.reduce(|a, b| format!("{a},{b}"))
|
||||
.ok_or(anyhow!("cannot list features"))?;
|
||||
log::info!("Available host features: {features}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
LocalDeviceCommand::List { path } => Ok(device.list(path)?),
|
||||
LocalDeviceCommand::Logcat { path } => {
|
||||
let writer: Box<dyn Write> = if let Some(path) = path {
|
||||
let f = File::create(path)?;
|
||||
Box::new(f)
|
||||
} else {
|
||||
Box::new(std::io::stdout())
|
||||
};
|
||||
Ok(device.get_logs(writer)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
7
adb_cli/src/handlers/mod.rs
Normal file
7
adb_cli/src/handlers/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod emulator_commands;
|
||||
mod host_commands;
|
||||
mod local_commands;
|
||||
|
||||
pub use emulator_commands::handle_emulator_commands;
|
||||
pub use host_commands::handle_host_commands;
|
||||
pub use local_commands::handle_local_commands;
|
||||
@@ -3,336 +3,68 @@
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
mod adb_termios;
|
||||
|
||||
mod commands;
|
||||
mod handlers;
|
||||
mod models;
|
||||
mod utils;
|
||||
|
||||
use adb_client::{
|
||||
ADBDeviceExt, ADBEmulatorDevice, ADBServer, ADBTcpDevice, ADBUSBDevice, DeviceShort,
|
||||
MDNSBackend, MDNSDiscoveryService,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use adb_client::{ADBDeviceExt, ADBServer, ADBTcpDevice, ADBUSBDevice, MDNSDiscoveryService};
|
||||
use adb_termios::ADBTermios;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use commands::{DeviceCommands, EmuCommand, HostCommand, LocalCommand, MdnsCommand};
|
||||
use models::{Command, Opts};
|
||||
use handlers::{handle_emulator_commands, handle_host_commands, handle_local_commands};
|
||||
use models::{DeviceCommands, LocalCommand, MainCommand, Opts};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use utils::setup_logger;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opts = Opts::parse();
|
||||
|
||||
// RUST_LOG variable has more priority then "--debug" flag
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
let level = match opts.debug {
|
||||
true => "trace",
|
||||
false => "info",
|
||||
};
|
||||
setup_logger(opts.debug);
|
||||
|
||||
std::env::set_var("RUST_LOG", level);
|
||||
}
|
||||
// Directly handling methods / commands that aren't linked to [`ADBDeviceExt`] trait.
|
||||
// Other methods just have to create a concrete [`ADBDeviceExt`] instance, and return it.
|
||||
// This instance will then be used to execute desired command.
|
||||
let (mut device, commands) = match opts.command {
|
||||
MainCommand::Host(server_command) => return Ok(handle_host_commands(server_command)?),
|
||||
MainCommand::Emu(emulator_command) => return handle_emulator_commands(emulator_command),
|
||||
MainCommand::Local(server_command) => {
|
||||
let mut adb_server = ADBServer::new(server_command.address);
|
||||
|
||||
// Setting default log level as "info" if not set
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
env_logger::init();
|
||||
|
||||
match opts.command {
|
||||
Command::Local(local) => {
|
||||
let mut adb_server = ADBServer::new(opts.address);
|
||||
|
||||
let mut device = match opts.serial {
|
||||
let device = match server_command.serial {
|
||||
Some(serial) => adb_server.get_device_by_name(&serial)?,
|
||||
None => adb_server.get_device()?,
|
||||
};
|
||||
|
||||
match local {
|
||||
LocalCommand::Pull { path, filename } => {
|
||||
let mut output = File::create(Path::new(&filename))?;
|
||||
device.pull(&path, &mut output)?;
|
||||
log::info!("Downloaded {path} as {filename}");
|
||||
}
|
||||
LocalCommand::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.push(&mut input, &path)?;
|
||||
log::info!("Uploaded {filename} to {path}");
|
||||
}
|
||||
LocalCommand::List { path } => {
|
||||
device.list(path)?;
|
||||
}
|
||||
LocalCommand::Stat { path } => {
|
||||
let stat_response = device.stat(path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
LocalCommand::Shell { commands } => {
|
||||
if commands.is_empty() {
|
||||
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
|
||||
// Using a scope here would call drop() too early..
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let mut adb_termios = adb_termios::ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
} else {
|
||||
device.shell_command(commands, std::io::stdout())?;
|
||||
}
|
||||
}
|
||||
LocalCommand::Run { package, activity } => {
|
||||
let output = device.run_activity(&package, &activity)?;
|
||||
std::io::stdout().write_all(&output)?;
|
||||
}
|
||||
LocalCommand::HostFeatures => {
|
||||
let features = device
|
||||
.host_features()?
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.reduce(|a, b| format!("{a},{b}"))
|
||||
.ok_or(anyhow!("cannot list features"))?;
|
||||
log::info!("Available host features: {features}");
|
||||
}
|
||||
LocalCommand::Reboot { reboot_type } => {
|
||||
log::info!("Reboots device in mode {:?}", reboot_type);
|
||||
device.reboot(reboot_type.into())?
|
||||
}
|
||||
LocalCommand::Framebuffer { path } => {
|
||||
device.framebuffer(&path)?;
|
||||
log::info!("Framebuffer dropped: {path}");
|
||||
}
|
||||
LocalCommand::Logcat { path } => {
|
||||
let writer: Box<dyn Write> = if let Some(path) = path {
|
||||
let f = File::create(path)?;
|
||||
Box::new(f)
|
||||
} else {
|
||||
Box::new(std::io::stdout())
|
||||
};
|
||||
device.get_logs(writer)?;
|
||||
}
|
||||
LocalCommand::Install { path } => {
|
||||
log::info!("Starting installation of APK {}...", path.display());
|
||||
device.install(path)?;
|
||||
match server_command.command {
|
||||
LocalCommand::DeviceCommands(device_commands) => (device.boxed(), device_commands),
|
||||
LocalCommand::LocalDeviceCommand(local_device_command) => {
|
||||
return handle_local_commands(device, local_device_command)
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Host(host) => {
|
||||
let mut adb_server = ADBServer::new(opts.address);
|
||||
|
||||
match host {
|
||||
HostCommand::Version => {
|
||||
let version = adb_server.version()?;
|
||||
log::info!("Android Debug Bridge version {}", version);
|
||||
log::info!("Package version {}-rust", std::env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
HostCommand::Kill => {
|
||||
adb_server.kill()?;
|
||||
}
|
||||
HostCommand::Devices { long } => {
|
||||
if long {
|
||||
log::info!("List of devices attached (extended)");
|
||||
for device in adb_server.devices_long()? {
|
||||
log::info!("{}", device);
|
||||
}
|
||||
} else {
|
||||
log::info!("List of devices attached");
|
||||
for device in adb_server.devices()? {
|
||||
log::info!("{}", device);
|
||||
}
|
||||
}
|
||||
}
|
||||
HostCommand::TrackDevices => {
|
||||
let callback = |device: DeviceShort| {
|
||||
log::info!("{}", device);
|
||||
Ok(())
|
||||
};
|
||||
log::info!("Live list of devices attached");
|
||||
adb_server.track_devices(callback)?;
|
||||
}
|
||||
HostCommand::Pair { address, code } => {
|
||||
adb_server.pair(address, code)?;
|
||||
log::info!("Paired device {address}");
|
||||
}
|
||||
HostCommand::Connect { address } => {
|
||||
adb_server.connect_device(address)?;
|
||||
log::info!("Connected to {address}");
|
||||
}
|
||||
HostCommand::Disconnect { address } => {
|
||||
adb_server.disconnect_device(address)?;
|
||||
log::info!("Disconnected {address}");
|
||||
}
|
||||
HostCommand::Mdns { subcommand } => match subcommand {
|
||||
MdnsCommand::Check => {
|
||||
let check = adb_server.mdns_check()?;
|
||||
let server_status = adb_server.server_status()?;
|
||||
match server_status.mdns_backend {
|
||||
MDNSBackend::Unknown => log::info!("unknown mdns backend..."),
|
||||
MDNSBackend::Bonjour => match check {
|
||||
true => log::info!("mdns daemon version [Bonjour]"),
|
||||
false => log::info!("ERROR: mdns daemon unavailable"),
|
||||
},
|
||||
MDNSBackend::OpenScreen => {
|
||||
log::info!("mdns daemon version [Openscreen discovery 0.0.0]")
|
||||
}
|
||||
}
|
||||
}
|
||||
MdnsCommand::Services => {
|
||||
log::info!("List of discovered mdns services");
|
||||
for service in adb_server.mdns_services()? {
|
||||
log::info!("{}", service);
|
||||
}
|
||||
}
|
||||
},
|
||||
HostCommand::ServerStatus => {
|
||||
log::info!("{}", adb_server.server_status()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Emu(emu) => {
|
||||
let mut emulator = match opts.serial {
|
||||
Some(serial) => ADBEmulatorDevice::new(serial, None)?,
|
||||
None => return Err(anyhow!("Serial must be set to use emulators !")),
|
||||
};
|
||||
|
||||
match emu {
|
||||
EmuCommand::Sms {
|
||||
phone_number,
|
||||
content,
|
||||
} => {
|
||||
emulator.send_sms(&phone_number, &content)?;
|
||||
log::info!("SMS sent to {phone_number}");
|
||||
}
|
||||
EmuCommand::Rotate => emulator.rotate()?,
|
||||
}
|
||||
}
|
||||
Command::Usb(usb) => {
|
||||
let mut device = match (usb.vendor_id, usb.product_id) {
|
||||
(Some(vid), Some(pid)) => match usb.path_to_private_key {
|
||||
MainCommand::Usb(usb_command) => {
|
||||
let device = match (usb_command.vendor_id, usb_command.product_id) {
|
||||
(Some(vid), Some(pid)) => match usb_command.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?,
|
||||
None => ADBUSBDevice::new(vid, pid)?,
|
||||
},
|
||||
|
||||
(None, None) => match usb.path_to_private_key {
|
||||
(None, None) => match usb_command.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?,
|
||||
None => ADBUSBDevice::autodetect()?,
|
||||
},
|
||||
|
||||
_ => {
|
||||
anyhow::bail!("please either supply values for both the --vendor-id and --product-id flags or none.");
|
||||
}
|
||||
};
|
||||
|
||||
match usb.commands {
|
||||
DeviceCommands::Shell { commands } => {
|
||||
if commands.is_empty() {
|
||||
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
|
||||
// Using a scope here would call drop() too early..
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let mut adb_termios = adb_termios::ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
} else {
|
||||
device.shell_command(commands, std::io::stdout())?;
|
||||
}
|
||||
}
|
||||
DeviceCommands::Pull {
|
||||
source,
|
||||
destination,
|
||||
} => {
|
||||
let mut output = File::create(Path::new(&destination))?;
|
||||
device.pull(&source, &mut output)?;
|
||||
log::info!("Downloaded {source} as {destination}");
|
||||
}
|
||||
DeviceCommands::Stat { path } => {
|
||||
let stat_response = device.stat(&path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
DeviceCommands::Reboot { reboot_type } => {
|
||||
log::info!("Reboots device in mode {:?}", reboot_type);
|
||||
device.reboot(reboot_type.into())?
|
||||
}
|
||||
DeviceCommands::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.push(&mut input, &path)?;
|
||||
log::info!("Uploaded {filename} to {path}");
|
||||
}
|
||||
DeviceCommands::Run { package, activity } => {
|
||||
let output = device.run_activity(&package, &activity)?;
|
||||
std::io::stdout().write_all(&output)?;
|
||||
}
|
||||
DeviceCommands::Install { path } => {
|
||||
log::info!("Starting installation of APK {}...", path.display());
|
||||
device.install(path)?;
|
||||
}
|
||||
DeviceCommands::Framebuffer { path } => device.framebuffer(path)?,
|
||||
}
|
||||
(device.boxed(), usb_command.commands)
|
||||
}
|
||||
Command::Tcp(tcp) => {
|
||||
let mut device = ADBTcpDevice::new(tcp.address)?;
|
||||
|
||||
match tcp.commands {
|
||||
DeviceCommands::Shell { commands } => {
|
||||
if commands.is_empty() {
|
||||
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
|
||||
// Using a scope here would call drop() too early..
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let mut adb_termios = adb_termios::ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
} else {
|
||||
device.shell_command(commands, std::io::stdout())?;
|
||||
}
|
||||
}
|
||||
DeviceCommands::Pull {
|
||||
source,
|
||||
destination,
|
||||
} => {
|
||||
let mut output = File::create(Path::new(&destination))?;
|
||||
device.pull(&source, &mut output)?;
|
||||
log::info!("Downloaded {source} as {destination}");
|
||||
}
|
||||
DeviceCommands::Stat { path } => {
|
||||
let stat_response = device.stat(&path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
DeviceCommands::Reboot { reboot_type } => {
|
||||
log::info!("Reboots device in mode {:?}", reboot_type);
|
||||
device.reboot(reboot_type.into())?
|
||||
}
|
||||
DeviceCommands::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.push(&mut input, &path)?;
|
||||
log::info!("Uploaded {filename} to {path}");
|
||||
}
|
||||
DeviceCommands::Run { package, activity } => {
|
||||
let output = device.run_activity(&package, &activity)?;
|
||||
std::io::stdout().write_all(&output)?;
|
||||
}
|
||||
DeviceCommands::Install { path } => {
|
||||
log::info!("Starting installation of APK {}...", path.display());
|
||||
device.install(path)?;
|
||||
}
|
||||
DeviceCommands::Framebuffer { path } => device.framebuffer(path)?,
|
||||
}
|
||||
MainCommand::Tcp(tcp_command) => {
|
||||
let device = ADBTcpDevice::new(tcp_command.address)?;
|
||||
(device.boxed(), tcp_command.commands)
|
||||
}
|
||||
Command::MdnsDiscovery => {
|
||||
MainCommand::Mdns => {
|
||||
let mut service = MDNSDiscoveryService::new()?;
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
@@ -347,8 +79,61 @@ fn main() -> Result<()> {
|
||||
)
|
||||
}
|
||||
|
||||
service.shutdown()?;
|
||||
return Ok(service.shutdown()?);
|
||||
}
|
||||
};
|
||||
|
||||
match commands {
|
||||
DeviceCommands::Shell { commands } => {
|
||||
if commands.is_empty() {
|
||||
// Need to duplicate some code here as ADBTermios [Drop] implementation resets terminal state.
|
||||
// Using a scope here would call drop() too early..
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let mut adb_termios = ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
device.shell(&mut std::io::stdin(), Box::new(std::io::stdout()))?;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
device.shell(std::io::stdin(), std::io::stdout())?;
|
||||
}
|
||||
} else {
|
||||
let commands: Vec<&str> = commands.iter().map(|v| v.as_str()).collect();
|
||||
device.shell_command(&commands, &mut std::io::stdout())?;
|
||||
}
|
||||
}
|
||||
DeviceCommands::Pull {
|
||||
source,
|
||||
destination,
|
||||
} => {
|
||||
let mut output = File::create(Path::new(&destination))?;
|
||||
device.pull(&source, &mut output)?;
|
||||
log::info!("Downloaded {source} as {destination}");
|
||||
}
|
||||
DeviceCommands::Stat { path } => {
|
||||
let stat_response = device.stat(&path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
DeviceCommands::Reboot { reboot_type } => {
|
||||
log::info!("Reboots device in mode {:?}", reboot_type);
|
||||
device.reboot(reboot_type.into())?
|
||||
}
|
||||
DeviceCommands::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename))?;
|
||||
device.push(&mut input, &path)?;
|
||||
log::info!("Uploaded {filename} to {path}");
|
||||
}
|
||||
DeviceCommands::Run { package, activity } => {
|
||||
let output = device.run_activity(&package, &activity)?;
|
||||
std::io::stdout().write_all(&output)?;
|
||||
}
|
||||
DeviceCommands::Install { path } => {
|
||||
log::info!("Starting installation of APK {}...", path.display());
|
||||
device.install(&path)?;
|
||||
}
|
||||
DeviceCommands::Framebuffer { path } => device.framebuffer(&path)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::models::RebootTypeCommand;
|
||||
use clap::Parser;
|
||||
|
||||
use super::RebootTypeCommand;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum LocalCommand {
|
||||
/// List available server features.
|
||||
HostFeatures,
|
||||
/// Push a file on device
|
||||
Push { filename: String, path: String },
|
||||
/// Pull a file from device
|
||||
Pull { path: String, filename: String },
|
||||
/// List a directory on device
|
||||
List { path: String },
|
||||
/// Stat a file specified on device
|
||||
Stat { path: String },
|
||||
pub enum DeviceCommands {
|
||||
/// Spawn an interactive shell or run a list of commands on the device
|
||||
Shell { commands: Vec<String> },
|
||||
/// Pull a file from device
|
||||
Pull { source: String, destination: String },
|
||||
/// Push a file on device
|
||||
Push { filename: String, path: String },
|
||||
/// Stat a file on device
|
||||
Stat { path: String },
|
||||
/// Run an activity on device specified by the intent
|
||||
Run {
|
||||
/// The package whose activity is to be invoked
|
||||
@@ -31,16 +28,14 @@ pub enum LocalCommand {
|
||||
#[clap(subcommand)]
|
||||
reboot_type: RebootTypeCommand,
|
||||
},
|
||||
/// Dump framebuffer of device
|
||||
Framebuffer { path: String },
|
||||
/// Get logs of device
|
||||
Logcat {
|
||||
/// Path to output file (created if not exists)
|
||||
path: Option<String>,
|
||||
},
|
||||
/// Install an APK on device
|
||||
Install {
|
||||
/// Path to APK file. Extension must be ".apk"
|
||||
path: PathBuf,
|
||||
},
|
||||
/// Dump framebuffer of device
|
||||
Framebuffer {
|
||||
/// Framebuffer image destination path
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
20
adb_cli/src/models/emu.rs
Normal file
20
adb_cli/src/models/emu.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct EmulatorCommand {
|
||||
#[clap(short = 's', long = "serial")]
|
||||
pub serial: String,
|
||||
#[clap(subcommand)]
|
||||
pub command: EmuCommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum EmuCommand {
|
||||
/// Send a SMS with given phone number and given content
|
||||
Sms {
|
||||
phone_number: String,
|
||||
content: String,
|
||||
},
|
||||
/// Rotate device screen from 90°
|
||||
Rotate,
|
||||
}
|
||||
24
adb_cli/src/models/local.rs
Normal file
24
adb_cli/src/models/local.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use clap::Parser;
|
||||
|
||||
use super::DeviceCommands;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum LocalCommand {
|
||||
#[clap(flatten)]
|
||||
DeviceCommands(DeviceCommands),
|
||||
#[clap(flatten)]
|
||||
LocalDeviceCommand(LocalDeviceCommand),
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum LocalDeviceCommand {
|
||||
/// List available server features.
|
||||
HostFeatures,
|
||||
/// List a directory on device
|
||||
List { path: String },
|
||||
/// Get logs of device
|
||||
Logcat {
|
||||
/// Path to output file (created if not exists)
|
||||
path: Option<String>,
|
||||
},
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
mod device;
|
||||
mod emu;
|
||||
mod host;
|
||||
mod local;
|
||||
mod opts;
|
||||
mod reboot_type;
|
||||
mod tcp;
|
||||
mod usb;
|
||||
|
||||
pub use opts::{Command, Opts};
|
||||
pub use device::DeviceCommands;
|
||||
pub use emu::{EmuCommand, EmulatorCommand};
|
||||
pub use host::{HostCommand, MdnsCommand};
|
||||
pub use local::{LocalCommand, LocalDeviceCommand};
|
||||
pub use opts::{MainCommand, Opts, ServerCommand};
|
||||
pub use reboot_type::RebootTypeCommand;
|
||||
pub use tcp::TcpCommand;
|
||||
pub use usb::UsbCommand;
|
||||
|
||||
@@ -1,36 +1,41 @@
|
||||
use std::net::SocketAddrV4;
|
||||
|
||||
use clap::Parser;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::commands::{EmuCommand, HostCommand, LocalCommand, TcpCommand, UsbCommand};
|
||||
use super::{EmulatorCommand, HostCommand, LocalCommand, TcpCommand, UsbCommand};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(about, version, author)]
|
||||
pub struct Opts {
|
||||
#[clap(long = "debug")]
|
||||
pub debug: bool,
|
||||
#[clap(subcommand)]
|
||||
pub command: MainCommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum MainCommand {
|
||||
/// Server related commands
|
||||
Host(ServerCommand<HostCommand>),
|
||||
/// Device related commands using server
|
||||
Local(ServerCommand<LocalCommand>),
|
||||
/// Emulator related commands
|
||||
Emu(EmulatorCommand),
|
||||
/// USB device related commands
|
||||
Usb(UsbCommand),
|
||||
/// TCP device related commands
|
||||
Tcp(TcpCommand),
|
||||
/// MDNS discovery related commands
|
||||
Mdns,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ServerCommand<T: Subcommand> {
|
||||
#[clap(short = 'a', long = "address", default_value = "127.0.0.1:5037")]
|
||||
pub address: SocketAddrV4,
|
||||
/// Serial id of a specific device. Every request will be sent to this device.
|
||||
#[clap(short = 's', long = "serial")]
|
||||
pub serial: Option<String>,
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum Command {
|
||||
#[clap(flatten)]
|
||||
Local(LocalCommand),
|
||||
#[clap(flatten)]
|
||||
Host(HostCommand),
|
||||
/// Emulator specific commands
|
||||
#[clap(subcommand)]
|
||||
Emu(EmuCommand),
|
||||
/// Device commands via USB, no server needed
|
||||
Usb(UsbCommand),
|
||||
/// Device commands via TCP, no server needed
|
||||
Tcp(TcpCommand),
|
||||
/// Discover devices over MDNS without using adb-server
|
||||
MdnsDiscovery,
|
||||
pub command: T,
|
||||
}
|
||||
|
||||
25
adb_cli/src/models/usb.rs
Normal file
25
adb_cli/src/models/usb.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::num::ParseIntError;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use super::DeviceCommands;
|
||||
|
||||
fn parse_hex_id(id: &str) -> Result<u16, ParseIntError> {
|
||||
u16::from_str_radix(id, 16)
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct UsbCommand {
|
||||
/// Hexadecimal vendor id of this USB device
|
||||
#[clap(short = 'v', long = "vendor-id", value_parser=parse_hex_id, value_name="VID")]
|
||||
pub vendor_id: Option<u16>,
|
||||
/// Hexadecimal product id of this USB device
|
||||
#[clap(short = 'p', long = "product-id", value_parser=parse_hex_id, value_name="PID")]
|
||||
pub product_id: Option<u16>,
|
||||
/// Path to a custom private key to use for authentication
|
||||
#[clap(short = 'k', long = "private-key")]
|
||||
pub path_to_private_key: Option<PathBuf>,
|
||||
#[clap(subcommand)]
|
||||
pub commands: DeviceCommands,
|
||||
}
|
||||
13
adb_cli/src/utils.rs
Normal file
13
adb_cli/src/utils.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub fn setup_logger(debug: bool) {
|
||||
// RUST_LOG variable has more priority then "--debug" flag
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
let level = match debug {
|
||||
true => "trace",
|
||||
false => "info",
|
||||
};
|
||||
|
||||
std::env::set_var("RUST_LOG", level);
|
||||
}
|
||||
|
||||
env_logger::init();
|
||||
}
|
||||
@@ -54,7 +54,7 @@ use adb_client::{ADBServer, ADBDeviceExt};
|
||||
|
||||
let mut server = ADBServer::default();
|
||||
let mut device = server.get_device().expect("cannot get device");
|
||||
device.shell_command(["df", "-h"],std::io::stdout());
|
||||
device.shell_command(&["df", "-h"], &mut std::io::stdout());
|
||||
```
|
||||
|
||||
#### Push a file to the device
|
||||
@@ -81,7 +81,7 @@ use adb_client::{ADBUSBDevice, ADBDeviceExt};
|
||||
let vendor_id = 0x04e8;
|
||||
let product_id = 0x6860;
|
||||
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
|
||||
device.shell_command(["df", "-h"],std::io::stdout());
|
||||
device.shell_command(&["df", "-h"], &mut std::io::stdout());
|
||||
```
|
||||
|
||||
#### (USB) Push a file to the device
|
||||
@@ -95,7 +95,7 @@ let vendor_id = 0x04e8;
|
||||
let product_id = 0x6860;
|
||||
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
|
||||
let mut input = File::open(Path::new("/tmp/f")).expect("Cannot open file");
|
||||
device.push(&mut input, "/data/local/tmp");
|
||||
device.push(&mut input, &"/data/local/tmp");
|
||||
```
|
||||
|
||||
#### (TCP) Get a shell from device
|
||||
@@ -107,5 +107,5 @@ use adb_client::{ADBTcpDevice, ADBDeviceExt};
|
||||
let device_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 10));
|
||||
let device_port = 43210;
|
||||
let mut device = ADBTcpDevice::new(SocketAddr::new(device_ip, device_port)).expect("cannot find device");
|
||||
device.shell(std::io::stdin(), std::io::stdout());
|
||||
device.shell(&mut std::io::stdin(), Box::new(std::io::stdout()));
|
||||
```
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use image::{ImageBuffer, ImageFormat, Rgba};
|
||||
|
||||
use crate::models::AdbStatResponse;
|
||||
use crate::{RebootType, Result};
|
||||
|
||||
/// Trait representing all features available on both [`crate::ADBServerDevice`] and [`crate::ADBUSBDevice`]
|
||||
pub trait ADBDeviceExt {
|
||||
/// Runs command in a shell on the device, and write its output and error streams into output.
|
||||
fn shell_command<S: ToString, W: Write>(
|
||||
&mut self,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
output: W,
|
||||
) -> Result<()>;
|
||||
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()>;
|
||||
|
||||
/// Starts an interactive shell session on the device.
|
||||
/// Input data is read from reader and write to writer.
|
||||
/// W has a 'static bound as it is internally used in a thread.
|
||||
fn shell<R: Read, W: Write + Send + 'static>(&mut self, reader: R, writer: W) -> Result<()>;
|
||||
fn shell(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()>;
|
||||
|
||||
/// Display the stat information for a remote file
|
||||
fn stat(&mut self, remote_path: &str) -> Result<AdbStatResponse>;
|
||||
|
||||
/// Pull the remote file pointed to by `source` and write its contents into `output`
|
||||
fn pull<A: AsRef<str>, W: Write>(&mut self, source: A, output: W) -> Result<()>;
|
||||
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()>;
|
||||
|
||||
/// Push `stream` to `path` on the device.
|
||||
fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()>;
|
||||
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()>;
|
||||
|
||||
/// Reboot the device using given reboot type
|
||||
fn reboot(&mut self, reboot_type: RebootType) -> Result<()>;
|
||||
@@ -34,7 +31,7 @@ pub trait ADBDeviceExt {
|
||||
fn run_activity(&mut self, package: &str, activity: &str) -> Result<Vec<u8>> {
|
||||
let mut output = Vec::new();
|
||||
self.shell_command(
|
||||
["am", "start", &format!("{package}/{package}.{activity}")],
|
||||
&["am", "start", &format!("{package}/{package}.{activity}")],
|
||||
&mut output,
|
||||
)?;
|
||||
|
||||
@@ -42,13 +39,35 @@ pub trait ADBDeviceExt {
|
||||
}
|
||||
|
||||
/// Install an APK pointed to by `apk_path` on device.
|
||||
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()>;
|
||||
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()>;
|
||||
|
||||
/// Inner method requesting framebuffer from an Android device
|
||||
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>>;
|
||||
|
||||
/// Dump framebuffer of this device into given path
|
||||
fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()>;
|
||||
fn framebuffer(&mut self, path: &dyn AsRef<Path>) -> Result<()> {
|
||||
// Big help from AOSP source code (<https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp>)
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.save(path.as_ref())?)
|
||||
}
|
||||
|
||||
/// Dump framebuffer of this device and return corresponding bytes.
|
||||
///
|
||||
/// Output data format is currently only `PNG`.
|
||||
fn framebuffer_bytes<W: Write + Seek>(&mut self, writer: W) -> Result<()>;
|
||||
fn framebuffer_bytes(&mut self) -> Result<Vec<u8>> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
let mut vec = Cursor::new(Vec::new());
|
||||
img.write_to(&mut vec, ImageFormat::Png)?;
|
||||
|
||||
Ok(vec.into_inner())
|
||||
}
|
||||
|
||||
/// Return a boxed instance representing this trait
|
||||
fn boxed(self) -> Box<dyn ADBDeviceExt>
|
||||
where
|
||||
Self: Sized,
|
||||
Self: 'static,
|
||||
{
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
use crate::{models::AdbStatResponse, ADBDeviceExt, ADBMessageTransport, RebootType, Result};
|
||||
use std::io::{Read, Write};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use super::ADBMessageDevice;
|
||||
|
||||
impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
|
||||
fn shell_command<S: ToString, W: Write>(
|
||||
&mut self,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
output: W,
|
||||
) -> Result<()> {
|
||||
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
|
||||
self.shell_command(command, output)
|
||||
}
|
||||
|
||||
fn shell<R: Read, W: Write + Send + 'static>(&mut self, reader: R, writer: W) -> Result<()> {
|
||||
fn shell(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()> {
|
||||
self.shell(reader, writer)
|
||||
}
|
||||
|
||||
@@ -20,11 +19,11 @@ impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
|
||||
self.stat(remote_path)
|
||||
}
|
||||
|
||||
fn pull<A: AsRef<str>, W: Write>(&mut self, source: A, output: W) -> Result<()> {
|
||||
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
|
||||
self.pull(source, output)
|
||||
}
|
||||
|
||||
fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
|
||||
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
|
||||
self.push(stream, path)
|
||||
}
|
||||
|
||||
@@ -32,15 +31,11 @@ impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
|
||||
self.reboot(reboot_type)
|
||||
}
|
||||
|
||||
fn install<P: AsRef<std::path::Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
|
||||
self.install(apk_path)
|
||||
}
|
||||
|
||||
fn framebuffer<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
|
||||
self.framebuffer(path)
|
||||
}
|
||||
|
||||
fn framebuffer_bytes<W: Write + std::io::Seek>(&mut self, writer: W) -> Result<()> {
|
||||
self.framebuffer_bytes(writer)
|
||||
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
|
||||
self.framebuffer_inner()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::{io::Read, net::SocketAddr};
|
||||
|
||||
use super::adb_message_device::ADBMessageDevice;
|
||||
use super::models::MessageCommand;
|
||||
@@ -67,20 +68,12 @@ impl ADBTcpDevice {
|
||||
|
||||
impl ADBDeviceExt for ADBTcpDevice {
|
||||
#[inline]
|
||||
fn shell_command<S: ToString, W: std::io::Write>(
|
||||
&mut self,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
output: W,
|
||||
) -> Result<()> {
|
||||
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
|
||||
self.inner.shell_command(command, output)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn shell<R: std::io::Read, W: std::io::Write + Send + 'static>(
|
||||
&mut self,
|
||||
reader: R,
|
||||
writer: W,
|
||||
) -> Result<()> {
|
||||
fn shell(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()> {
|
||||
self.inner.shell(reader, writer)
|
||||
}
|
||||
|
||||
@@ -90,12 +83,12 @@ impl ADBDeviceExt for ADBTcpDevice {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pull<A: AsRef<str>, W: std::io::Write>(&mut self, source: A, output: W) -> Result<()> {
|
||||
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
|
||||
self.inner.pull(source, output)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push<R: std::io::Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
|
||||
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
|
||||
self.inner.push(stream, path)
|
||||
}
|
||||
|
||||
@@ -105,18 +98,13 @@ impl ADBDeviceExt for ADBTcpDevice {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
|
||||
self.inner.install(apk_path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
self.inner.framebuffer(path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn framebuffer_bytes<W: std::io::Write + std::io::Seek>(&mut self, writer: W) -> Result<()> {
|
||||
self.inner.framebuffer_bytes(writer)
|
||||
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
|
||||
self.inner.framebuffer_inner()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ use rusb::Device;
|
||||
use rusb::DeviceDescriptor;
|
||||
use rusb::UsbContext;
|
||||
use std::fs::read_to_string;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
@@ -249,20 +251,12 @@ impl ADBUSBDevice {
|
||||
|
||||
impl ADBDeviceExt for ADBUSBDevice {
|
||||
#[inline]
|
||||
fn shell_command<S: ToString, W: std::io::Write>(
|
||||
&mut self,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
output: W,
|
||||
) -> Result<()> {
|
||||
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
|
||||
self.inner.shell_command(command, output)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn shell<R: std::io::Read, W: std::io::Write + Send + 'static>(
|
||||
&mut self,
|
||||
reader: R,
|
||||
writer: W,
|
||||
) -> Result<()> {
|
||||
fn shell<'a>(&mut self, reader: &mut dyn Read, writer: Box<(dyn Write + Send)>) -> Result<()> {
|
||||
self.inner.shell(reader, writer)
|
||||
}
|
||||
|
||||
@@ -272,12 +266,12 @@ impl ADBDeviceExt for ADBUSBDevice {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pull<A: AsRef<str>, W: std::io::Write>(&mut self, source: A, output: W) -> Result<()> {
|
||||
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
|
||||
self.inner.pull(source, output)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push<R: std::io::Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
|
||||
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
|
||||
self.inner.push(stream, path)
|
||||
}
|
||||
|
||||
@@ -287,18 +281,13 @@ impl ADBDeviceExt for ADBUSBDevice {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
|
||||
self.inner.install(apk_path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
self.inner.framebuffer(path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn framebuffer_bytes<W: std::io::Write + std::io::Seek>(&mut self, writer: W) -> Result<()> {
|
||||
self.inner.framebuffer_bytes(writer)
|
||||
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
|
||||
self.inner.framebuffer_inner()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use image::{ImageBuffer, ImageFormat, Rgba};
|
||||
use image::{ImageBuffer, Rgba};
|
||||
|
||||
use crate::{
|
||||
device::{adb_message_device::ADBMessageDevice, MessageCommand},
|
||||
@@ -10,17 +10,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
|
||||
pub fn framebuffer<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.save(path.as_ref())?)
|
||||
}
|
||||
|
||||
pub fn framebuffer_bytes<W: Write + std::io::Seek>(&mut self, mut writer: W) -> Result<()> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.write_to(&mut writer, ImageFormat::Png)?)
|
||||
}
|
||||
|
||||
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
pub(crate) fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
self.open_session(b"framebuffer:\0")?;
|
||||
|
||||
let response = self.recv_and_reply_okay()?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fs::File;
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::{
|
||||
};
|
||||
|
||||
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
|
||||
pub(crate) fn install<P: AsRef<std::path::Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
let mut apk_file = File::open(&apk_path)?;
|
||||
pub(crate) fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
|
||||
let mut apk_file = File::open(apk_path)?;
|
||||
|
||||
check_extension_is_apk(&apk_path)?;
|
||||
check_extension_is_apk(apk_path)?;
|
||||
|
||||
let file_size = apk_file.metadata()?.len();
|
||||
|
||||
|
||||
@@ -9,22 +9,8 @@ use crate::{
|
||||
|
||||
impl<T: ADBMessageTransport> ADBMessageDevice<T> {
|
||||
/// Runs 'command' in a shell on the device, and write its output and error streams into output.
|
||||
pub(crate) fn shell_command<S: ToString, W: Write>(
|
||||
&mut self,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
mut output: W,
|
||||
) -> Result<()> {
|
||||
let response = self.open_session(
|
||||
format!(
|
||||
"shell:{}\0",
|
||||
command
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
pub(crate) fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
|
||||
let response = self.open_session(format!("shell:{}\0", command.join(" "),).as_bytes())?;
|
||||
|
||||
if response.header().command() != MessageCommand::Okay {
|
||||
return Err(RustADBError::ADBRequestFailed(format!(
|
||||
@@ -47,11 +33,10 @@ impl<T: ADBMessageTransport> ADBMessageDevice<T> {
|
||||
|
||||
/// Starts an interactive shell session on the device.
|
||||
/// Input data is read from [reader] and write to [writer].
|
||||
/// [W] has a 'static bound as it is internally used in a thread.
|
||||
pub(crate) fn shell<R: Read, W: Write + Send + 'static>(
|
||||
pub(crate) fn shell(
|
||||
&mut self,
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
mut reader: &mut dyn Read,
|
||||
mut writer: Box<(dyn Write + Send)>,
|
||||
) -> Result<()> {
|
||||
self.open_session(b"shell:\0")?;
|
||||
|
||||
|
||||
@@ -12,11 +12,7 @@ use crate::{
|
||||
use super::ADBServerDevice;
|
||||
|
||||
impl ADBDeviceExt for ADBServerDevice {
|
||||
fn shell_command<S: ToString, W: Write>(
|
||||
&mut self,
|
||||
command: impl IntoIterator<Item = S>,
|
||||
mut output: W,
|
||||
) -> Result<()> {
|
||||
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
|
||||
let supported_features = self.host_features()?;
|
||||
if !supported_features.contains(&HostFeatures::ShellV2)
|
||||
&& !supported_features.contains(&HostFeatures::Cmd)
|
||||
@@ -28,13 +24,7 @@ impl ADBDeviceExt for ADBServerDevice {
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::ShellCommand(
|
||||
command
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
))?;
|
||||
.send_adb_request(AdbServerCommand::ShellCommand(command.join(" ")))?;
|
||||
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
loop {
|
||||
@@ -62,10 +52,10 @@ impl ADBDeviceExt for ADBServerDevice {
|
||||
self.stat(remote_path)
|
||||
}
|
||||
|
||||
fn shell<R: Read, W: Write + Send + 'static>(
|
||||
fn shell(
|
||||
&mut self,
|
||||
mut reader: R,
|
||||
mut writer: W,
|
||||
mut reader: &mut dyn Read,
|
||||
mut writer: Box<(dyn Write + Send)>,
|
||||
) -> Result<()> {
|
||||
let supported_features = self.host_features()?;
|
||||
if !supported_features.contains(&HostFeatures::ShellV2)
|
||||
@@ -115,7 +105,7 @@ impl ADBDeviceExt for ADBServerDevice {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pull<A: AsRef<str>, W: Write>(&mut self, source: A, mut output: W) -> Result<()> {
|
||||
fn pull(&mut self, source: &dyn AsRef<str>, mut output: &mut dyn Write) -> Result<()> {
|
||||
self.pull(source, &mut output)
|
||||
}
|
||||
|
||||
@@ -123,19 +113,15 @@ impl ADBDeviceExt for ADBServerDevice {
|
||||
self.reboot(reboot_type)
|
||||
}
|
||||
|
||||
fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
|
||||
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
|
||||
self.push(stream, path)
|
||||
}
|
||||
|
||||
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
|
||||
self.install(apk_path)
|
||||
}
|
||||
|
||||
fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
self.framebuffer(path)
|
||||
}
|
||||
|
||||
fn framebuffer_bytes<W: Write + std::io::Seek>(&mut self, writer: W) -> Result<()> {
|
||||
self.framebuffer_bytes(writer)
|
||||
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
|
||||
self.framebuffer_inner()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use std::{
|
||||
io::{Read, Seek, Write},
|
||||
path::Path,
|
||||
};
|
||||
use std::io::Read;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use image::{ImageBuffer, ImageFormat, Rgba};
|
||||
use image::{ImageBuffer, Rgba};
|
||||
|
||||
use crate::{
|
||||
models::{AdbServerCommand, FrameBufferInfoV1, FrameBufferInfoV2},
|
||||
@@ -12,23 +9,8 @@ use crate::{
|
||||
};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Dump framebuffer of this device into given ['path']
|
||||
/// Big help from source code (<https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp>)
|
||||
pub(crate) fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.save(path.as_ref())?)
|
||||
}
|
||||
|
||||
/// Dump framebuffer of this device and return corresponding bytes.
|
||||
///
|
||||
/// Output data format is currently only `PNG`.
|
||||
pub(crate) fn framebuffer_bytes<W: Write + Seek>(&mut self, mut writer: W) -> Result<()> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.write_to(&mut writer, ImageFormat::Png)?)
|
||||
}
|
||||
|
||||
/// Inner method requesting framebuffer from Android device
|
||||
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
pub(crate) fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
let serial: String = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
@@ -51,6 +51,6 @@ impl<W: Write> Write for LogFilter<W> {
|
||||
impl ADBServerDevice {
|
||||
/// Get logs from device
|
||||
pub fn get_logs<W: Write>(&mut self, output: W) -> Result<()> {
|
||||
self.shell_command(["exec logcat"], LogFilter::new(output))
|
||||
self.shell_command(&["exec logcat"], &mut LogFilter::new(output))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ impl<R: Read> Read for ADBRecvCommandReader<R> {
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Receives path to stream from the device.
|
||||
pub fn pull<A: AsRef<str>>(&mut self, path: A, stream: &mut dyn Write) -> Result<()> {
|
||||
pub fn pull(&mut self, path: &dyn AsRef<str>, stream: &mut dyn Write) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
Reference in New Issue
Block a user