use super::common::{Emoji, SystemCommand};
use crate::cli::{BtAction, DevAction, DevArgs, PrintAction};
use crate::os::{DevManager, DeviceInfo, Domain, ExecutableCommand, OutputFormat, PrinterInfo};
use anyhow::Result;
use clap::{ArgMatches, Args, Command as ClapCommand, FromArgMatches};
use std::process::Command;
pub struct StandardDev;
impl Domain for StandardDev {
fn name(&self) -> &'static str {
"dev"
}
fn command(&self) -> ClapCommand {
DevArgs::augment_args(ClapCommand::new("dev").about("Manage connected devices"))
}
fn execute(
&self,
matches: &ArgMatches,
_app: &ClapCommand,
) -> Result<Box<dyn ExecutableCommand>> {
let args = DevArgs::from_arg_matches(matches)?;
match &args.action {
Some(DevAction::Ls { format }) => self.ls(*format),
Some(DevAction::Pci { format }) => self.pci(*format),
Some(DevAction::Usb { format }) => self.usb(*format),
Some(DevAction::Bt { action }) => match action {
BtAction::Status => self.bt_status(),
BtAction::Scan => self.bt_scan(),
BtAction::Pair { address } => self.bt_pair(address),
BtAction::Connect { address } => self.bt_connect(address),
},
Some(DevAction::Print { action }) => match action {
PrintAction::Ls { format } => self.ls_printers(*format),
},
None => self.ls(OutputFormat::Table),
}
}
}
impl DevManager for StandardDev {
fn ls(&self, format: OutputFormat) -> Result<Box<dyn ExecutableCommand>> {
Ok(Box::new(DevListAllCommand { format }))
}
fn pci(&self, format: OutputFormat) -> Result<Box<dyn ExecutableCommand>> {
if format == OutputFormat::Original {
return Ok(Box::new(SystemCommand::new("lspci")));
}
Ok(Box::new(DevPciCommand { format }))
}
fn usb(&self, format: OutputFormat) -> Result<Box<dyn ExecutableCommand>> {
if format == OutputFormat::Original {
return Ok(Box::new(SystemCommand::new("lsusb")));
}
Ok(Box::new(DevUsbCommand { format }))
}
fn bt_status(&self) -> Result<Box<dyn ExecutableCommand>> {
Ok(Box::new(SystemCommand::new("bluetoothctl").arg("show")))
}
fn bt_scan(&self) -> Result<Box<dyn ExecutableCommand>> {
Ok(Box::new(
SystemCommand::new("bluetoothctl").arg("scan").arg("on"),
))
}
fn bt_pair(&self, address: &str) -> Result<Box<dyn ExecutableCommand>> {
Ok(Box::new(
SystemCommand::new("bluetoothctl")
.arg("pair")
.arg("--")
.arg(address),
))
}
fn bt_connect(&self, address: &str) -> Result<Box<dyn ExecutableCommand>> {
Ok(Box::new(
SystemCommand::new("bluetoothctl")
.arg("connect")
.arg("--")
.arg(address),
))
}
fn ls_printers(&self, format: OutputFormat) -> Result<Box<dyn ExecutableCommand>> {
if format == OutputFormat::Original {
return Ok(Box::new(SystemCommand::new("lpstat").arg("-p")));
}
Ok(Box::new(DevPrintersCommand { format }))
}
}
pub struct DevListAllCommand {
pub format: OutputFormat,
}
impl ExecutableCommand for DevListAllCommand {
fn execute(&self) -> Result<()> {
let mut devices = Vec::new();
if let Ok(output) = Command::new("lspci").arg("-mm").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let parts: Vec<&str> = line.split("\" \"").collect();
if parts.len() >= 4 {
devices.push(DeviceInfo {
bus: "PCI".to_string(),
device: parts[1].trim_matches('"').to_string(),
id: parts[2].trim_matches('"').to_string(),
description: parts[3..].join(" ").trim_matches('"').to_string(),
});
}
}
}
if let Ok(output) = Command::new("lsusb").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 6 {
devices.push(DeviceInfo {
bus: "USB".to_string(),
device: "Device".to_string(),
id: parts[5].to_string(),
description: parts[6..].join(" "),
});
}
}
}
if let Ok(output) = Command::new("lpstat").arg("-p").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
devices.push(DeviceInfo {
bus: "Printer".to_string(),
device: "Printer".to_string(),
id: parts[1].to_string(),
description: parts[2..].join(" "),
});
}
}
}
match self.format {
OutputFormat::Table => {
let mut table = comfy_table::Table::new();
if let Ok((width, _)) = crossterm::terminal::size() {
table.set_width(width);
}
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["", "Type", "Class", "ID", "Description"]);
for d in devices {
let emoji = match d.bus.as_str() {
"PCI" => Emoji::Pci.get(),
"USB" => Emoji::Usb.get(),
"Printer" => Emoji::Printer.get(),
_ => "📟",
};
table.add_row(vec![emoji, &d.bus, &d.device, &d.id, &d.description]);
}
println!("=== Connected Devices (🏗️ PCI / 🔌 USB / 🖨️ Printer) ===");
println!("{}", table);
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&devices)?);
}
OutputFormat::Yaml => {
println!("{}", serde_yaml::to_string(&devices)?);
}
OutputFormat::Original => {
for d in devices {
println!("[{}] {} {} {}", d.bus, d.id, d.device, d.description);
}
}
}
Ok(())
}
fn as_string(&self) -> String {
"lspci -mm && lsusb".to_string()
}
}
pub struct DevPciCommand {
pub format: OutputFormat,
}
impl ExecutableCommand for DevPciCommand {
fn execute(&self) -> Result<()> {
let output = Command::new("lspci").arg("-mm").output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split("\" \"").collect();
if parts.len() >= 4 {
devices.push(DeviceInfo {
bus: parts[0].trim_matches('"').to_string(),
device: parts[1].trim_matches('"').to_string(),
id: parts[2].trim_matches('"').to_string(),
description: parts[3..].join(" ").trim_matches('"').to_string(),
});
}
}
match self.format {
OutputFormat::Table => {
let mut table = comfy_table::Table::new();
if let Ok((width, _)) = crossterm::terminal::size() {
table.set_width(width);
}
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["Bus", "Class", "Vendor", "Device"]);
for d in devices {
table.add_row(vec![d.bus, d.device, d.id, d.description]);
}
println!("{}", table);
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&devices)?);
}
OutputFormat::Yaml => {
println!("{}", serde_yaml::to_string(&devices)?);
}
OutputFormat::Original => unreachable!(),
}
Ok(())
}
fn as_string(&self) -> String {
"lspci -mm".to_string()
}
}
pub struct DevUsbCommand {
pub format: OutputFormat,
}
impl ExecutableCommand for DevUsbCommand {
fn execute(&self) -> Result<()> {
let output = Command::new("lsusb").output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 6 {
devices.push(DeviceInfo {
bus: parts[1].to_string(),
device: parts[3].trim_matches(':').to_string(),
id: parts[5].to_string(),
description: parts[6..].join(" "),
});
}
}
match self.format {
OutputFormat::Table => {
let mut table = comfy_table::Table::new();
if let Ok((width, _)) = crossterm::terminal::size() {
table.set_width(width);
}
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["Bus", "Device", "ID", "Description"]);
for d in devices {
table.add_row(vec![d.bus, d.device, d.id, d.description]);
}
println!("{}", table);
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&devices)?);
}
OutputFormat::Yaml => {
println!("{}", serde_yaml::to_string(&devices)?);
}
OutputFormat::Original => unreachable!(),
}
Ok(())
}
fn as_string(&self) -> String {
"lsusb".to_string()
}
}
pub struct DevPrintersCommand {
pub format: OutputFormat,
}
impl ExecutableCommand for DevPrintersCommand {
fn execute(&self) -> Result<()> {
let output = Command::new("lpstat").arg("-p").output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut printers = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
printers.push(PrinterInfo {
name: parts[1].to_string(),
status: parts[2..].join(" "),
});
}
}
match self.format {
OutputFormat::Table => {
let mut table = comfy_table::Table::new();
if let Ok((width, _)) = crossterm::terminal::size() {
table.set_width(width);
}
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["Printer", "Status"]);
for p in printers {
table.add_row(vec![p.name, p.status]);
}
println!("{}", table);
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&printers)?);
}
OutputFormat::Yaml => {
println!("{}", serde_yaml::to_string(&printers)?);
}
OutputFormat::Original => unreachable!(),
}
Ok(())
}
fn as_string(&self) -> String {
"lpstat -p".to_string()
}
}