Feature/Multi-device support (#49)

This commit is contained in:
0x192 2021-10-22 23:34:42 +02:00
commit e49df41534
No known key found for this signature in database
GPG key ID: 34D27465928A0A1D
3 changed files with 119 additions and 37 deletions

View file

@ -4,28 +4,37 @@ use crate::gui::widgets::package_row::PackageRow;
use regex::Regex;
use static_init::dynamic;
use std::collections::HashSet;
use std::env;
use std::process::Command;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Phone {
pub model: String,
pub android_sdk: u8,
pub user_list: Vec<User>,
pub adb_id: String,
}
impl Default for Phone {
fn default() -> Self {
Self {
model: get_phone_brand(),
android_sdk: get_android_sdk(),
user_list: get_user_list(),
model: "fetching devices...".to_string(),
android_sdk: 0,
user_list: vec![],
adb_id: "".to_string(),
}
}
}
impl std::fmt::Display for Phone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.model.to_string(),)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct User {
pub id: u16,
@ -37,18 +46,24 @@ impl std::fmt::Display for User {
write!(f, "{}", format!("user {}", self.id),)
}
}
pub fn adb_shell_command(args: &str) -> Result<String, String> {
pub fn adb_shell_command(shell: bool, args: &str) -> Result<String, String> {
let adb_command = match shell {
true => vec!["shell", args],
false => vec![args],
};
#[cfg(target_os = "windows")]
let output = Command::new("adb")
.args(&["shell", args])
.args(adb_command)
.creation_flags(0x08000000) // do not open a cmd window
.output();
#[cfg(target_os = "macos")]
let output = Command::new("adb").args(&["shell", args]).output();
let output = Command::new("adb").args(adb_command).output();
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let output = Command::new("adb").args(&["shell", args]).output();
let output = Command::new("adb").args(adb_command).output();
match output {
Err(e) => {
@ -76,7 +91,7 @@ pub fn list_all_system_packages(user_id: &Option<&User>) -> String {
None => "pm list packages -s -u".to_string(),
};
adb_shell_command(&action)
adb_shell_command(true, &action)
.unwrap_or_else(|_| "".to_string())
.replace("package:", "")
}
@ -93,7 +108,7 @@ pub fn hashset_system_packages(state: PackageState, user_id: &Option<&User>) ->
_ => "".to_string(), // You probably don't need to use this function for anything else
};
adb_shell_command(&action)
adb_shell_command(true, &action)
.unwrap_or_default()
.replace("package:", "")
.lines()
@ -197,7 +212,7 @@ pub fn action_handler(
};
for action in actions {
match adb_shell_command(&action) {
match adb_shell_command(true, &action) {
Ok(_) => {
info!("[{}] {}", package.removal, action);
}
@ -214,7 +229,7 @@ pub fn action_handler(
}
pub fn get_phone_model() -> String {
match adb_shell_command("getprop ro.product.model") {
match adb_shell_command(true, "getprop ro.product.model") {
Ok(model) => model,
Err(err) => {
println!("ERROR: {}", err);
@ -228,7 +243,7 @@ pub fn get_phone_model() -> String {
}
pub fn get_android_sdk() -> u8 {
match adb_shell_command("getprop ro.build.version.sdk") {
match adb_shell_command(true, "getprop ro.build.version.sdk") {
Ok(sdk) => sdk.parse().unwrap(),
Err(_) => 0,
}
@ -237,7 +252,7 @@ pub fn get_android_sdk() -> u8 {
pub fn get_phone_brand() -> String {
format!(
"{} {}",
adb_shell_command("getprop ro.product.brand")
adb_shell_command(true, "getprop ro.product.brand")
.unwrap_or_else(|_| "".to_string())
.trim(),
get_phone_model()
@ -247,8 +262,7 @@ pub fn get_phone_brand() -> String {
pub fn get_user_list() -> Vec<User> {
#[dynamic]
static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap();
match adb_shell_command("pm list users") {
match adb_shell_command(true, "pm list users") {
Ok(users) => RE
.find_iter(&users)
.enumerate()
@ -260,3 +274,30 @@ pub fn get_user_list() -> Vec<User> {
Err(_) => vec![],
}
}
pub fn get_device_list() -> Vec<Phone> {
#[dynamic]
static RE: Regex = Regex::new(r"\n([[:alnum:]]+)\s+device").unwrap();
match adb_shell_command(false, "devices") {
Ok(devices) => {
let mut device_list: Vec<Phone> = vec![];
for device in RE.captures_iter(&devices) {
env::set_var("ANDROID_SERIAL", device[1].to_string());
device_list.push(Phone {
model: get_phone_brand(),
android_sdk: get_android_sdk(),
user_list: get_user_list(),
adb_id: device[1].to_string(),
});
}
device_list
}
Err(err) => {
warn!("get_device_list() -> {}", err);
vec![]
}
}
}

View file

@ -2,16 +2,17 @@ pub mod style;
pub mod views;
pub mod widgets;
pub use crate::core::sync::Phone;
pub use crate::core::sync::{get_device_list, Phone};
pub use crate::core::uad_lists::Package;
use crate::core::utils::icon;
use std::env;
pub use views::about::About as AboutView;
pub use views::list::{List as AppsView, Message as AppsMessage};
pub use views::settings::{Message as SettingsMessage, Settings as SettingsView};
use iced::{
button, window::Settings as Window, Alignment, Application, Button, Column, Command, Container,
Element, Font, Length, Row, Settings, Space, Text,
button, pick_list, window::Settings as Window, Alignment, Application, Button, Column, Command,
Container, Element, Font, Length, PickList, Row, Settings, Space, Text
};
pub const ICONS: Font = Font::External {
@ -34,7 +35,6 @@ impl Default for View {
#[derive(Debug, Default)]
pub struct UadGui {
phone: Phone,
view: View,
apps_view: AppsView,
about_view: AboutView,
@ -43,6 +43,9 @@ pub struct UadGui {
settings_btn: button::State,
apps_btn: button::State,
apps_refresh_btn: button::State,
device_picklist: pick_list::State<Phone>,
device_list: Vec<Phone>,
selected_device: Phone,
}
#[derive(Debug, Clone)]
@ -50,12 +53,14 @@ pub enum Message {
// Navigation Panel
AboutPressed,
SettingsPressed,
AppsRefreshPress,
LoadDevices(usize),
AppsPress,
DeviceSelected(Phone),
AppsAction(AppsMessage),
SettingsAction(SettingsMessage),
Init(AppsMessage),
RefreshButtonPressed,
Init,
}
impl Application for UadGui {
@ -66,7 +71,7 @@ impl Application for UadGui {
fn new(_flags: ()) -> (Self, Command<Message>) {
(
Self::default(),
Command::perform(Self::load_phone_packages(), Message::Init),
Command::perform(Self::init(), |_| { Message::Init }),
)
}
@ -76,20 +81,28 @@ impl Application for UadGui {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Init(_) => {
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.phone.android_sdk, self.phone.model
);
Command::perform(Self::load_phone_packages(), Message::AppsAction)
Message::Init => {
Command::perform(Self::refresh(10), Message::LoadDevices)
}
Message::AppsRefreshPress => {
self.phone = Phone::default();
Message::RefreshButtonPressed => {
// Save the current selected device
let i = self.device_list.iter().position(|phone| *phone == self.selected_device).unwrap();
self.device_list = vec![Phone::default()];
self.selected_device = self.device_list[0].clone();
Command::perform(Self::refresh(i), Message::LoadDevices)
}
Message::LoadDevices(old_selected_device) => {
self.settings_view = SettingsView::default();
self.device_list = get_device_list();
self.selected_device = match old_selected_device < self.device_list.len() {
true => self.device_list[old_selected_device].clone(),
false => self.device_list.last().unwrap().clone(),
};
env::set_var("ANDROID_SERIAL", self.selected_device.adb_id.clone());
info!("{:-^65}", "-");
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.phone.android_sdk, self.phone.model
self.selected_device.android_sdk, self.selected_device.model
);
self.apps_view = AppsView::default();
self.view = View::List;
@ -109,12 +122,24 @@ impl Application for UadGui {
}
Message::AppsAction(msg) => self
.apps_view
.update(&self.settings_view, &mut self.phone, msg)
.update(&self.settings_view, &mut self.selected_device, msg)
.map(Message::AppsAction),
Message::SettingsAction(msg) => {
self.settings_view.update(msg);
Command::none()
}
Message::DeviceSelected(device) => {
self.selected_device = device;
env::set_var("ANDROID_SERIAL", self.selected_device.adb_id.clone());
info!("{:-^65}", "-");
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.selected_device.android_sdk, self.selected_device.model
);
self.apps_view = AppsView::default();
self.view = View::List;
Command::perform(Self::load_phone_packages(), Message::AppsAction)
}
}
}
@ -125,10 +150,18 @@ impl Application for UadGui {
.style(style::PrimaryButton(self.settings_view.theme.palette));
let apps_refresh_btn = Button::new(&mut self.apps_refresh_btn, refresh_icon())
.on_press(Message::AppsRefreshPress)
.on_press(Message::RefreshButtonPressed)
.padding(5)
.style(style::RefreshButton(self.settings_view.theme.palette));
let device_picklist = PickList::new(
&mut self.device_picklist,
&self.device_list,
Some(self.selected_device.clone()),
Message::DeviceSelected,
)
.style(style::PickList(self.settings_view.theme.palette));
let uad_version = Text::new(env!("CARGO_PKG_VERSION"));
let about_btn = Button::new(&mut self.about_btn, Text::new("About"))
@ -146,7 +179,7 @@ impl Application for UadGui {
.align_items(Alignment::Center)
.spacing(10)
.push(apps_refresh_btn)
.push(Text::new("Device: ".to_string() + &self.phone.model))
.push(device_picklist)
.push(Space::new(Length::Fill, Length::Shrink))
.push(uad_version)
.push(apps_btn)
@ -161,7 +194,7 @@ impl Application for UadGui {
let main_container = match self.view {
View::List => self
.apps_view
.view(&self.settings_view, &self.phone)
.view(&self.settings_view, &self.selected_device)
.map(Message::AppsAction),
View::About => self.about_view.view(&self.settings_view),
View::Settings => self.settings_view.view().map(Message::SettingsAction),
@ -193,6 +226,14 @@ impl UadGui {
pub async fn load_phone_packages() -> AppsMessage {
AppsMessage::LoadPackages
}
pub async fn init() -> Message {
Message::Init
}
pub async fn refresh(i: usize) -> usize {
i
}
}
fn refresh_icon() -> Text {

View file

@ -1,3 +1,3 @@
pub mod about;
pub mod list;
pub mod settings;
pub mod settings;