pub mod connector;
use std::process::Command;
use std::string::String;
use crate::connector::*;
#[derive(Default, Debug, serde::Serialize, serde::Deserialize)]
pub struct Parser {
pub outputs: Vec<String>,
pub connected_outputs: Vec<String>,
pub connectors: Vec<Connector>,
pub screen: Screen,
}
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Screen {
pub minimum: Resolution,
pub current: Resolution,
pub maximum: Resolution,
}
impl Parser {
pub fn new() -> Parser {
Parser::default()
}
pub fn parse(&mut self) -> Result<(), String> {
self.outputs = Vec::new();
self.connectors = Vec::new();
self.connected_outputs = Vec::new();
self.connectors = match Self::parse_query(self) {
Ok(r) => r,
Err(e) => return Err(e),
};
self.outputs = self.connectors.iter().map(|c| c.name.to_string()).collect();
self.connected_outputs = self
.connectors
.iter()
.filter(|c| c.status == "connected")
.map(|c| c.name.to_string())
.collect();
Ok(())
}
#[cfg(not(feature = "test"))]
fn exec_command() -> Result<String, String> {
let mut cmd = Command::new("sh");
cmd.arg("-c");
cmd.arg("xrandr --query | tr -s ' '");
let output = match cmd.output() {
Ok(r) => r,
Err(e) => return Err(e.to_string()),
};
if let Some(code) = output.status.code() {
if code != 0 {
let err_string = match String::from_utf8(output.stderr) {
Ok(r) => r,
Err(e) => return Err(e.to_string()),
};
return Err(err_string.to_string());
}
}
match String::from_utf8(output.stdout) {
Ok(r) => Ok(r),
Err(e) => Err(e.to_string()),
}
}
#[cfg(feature = "test")]
fn exec_command() -> Result<String, String> {
use std::env;
let mut cmd = Command::new("sh");
cmd.arg("-c");
let mut example_dir: String = "".to_string();
for (key, value) in env::vars() {
if key == "EXAMPLE_DIR" {
example_dir = value;
}
}
cmd.env("EXAMPLE_DIR", example_dir);
cmd.arg("cat $EXAMPLE_DIR/example_output");
let output = match cmd.output() {
Ok(r) => r,
Err(e) => return Err(e.to_string()),
};
if let Some(code) = output.status.code() {
if code != 0 {
let err_string = match String::from_utf8(output.stderr) {
Ok(r) => r,
Err(e) => return Err(e.to_string()),
};
return Err(err_string.to_string());
}
}
match String::from_utf8(output.stdout) {
Ok(r) => Ok(r),
Err(e) => return Err(e.to_string()),
}
}
fn parse_query(&mut self) -> Result<Vec<Connector>, String> {
let out_string = Self::exec_command()?;
let mut output: Vec<String> = out_string.split("\n").map(|s| s.to_string()).collect();
output.retain(|d| d != "");
let mut connectors: Vec<Connector> = Vec::new();
let mut active: Connector = Connector::default();
for o in &output {
let mut o_vec: Vec<&str> = o.split(" ").collect();
o_vec.retain(|s| s != &"");
if o_vec.contains(&"Screen") {
self.screen = Screen {
minimum: Resolution {
horizontal: o_vec[3].to_string(),
vertical: o_vec[5].replace(",", ""),
},
current: Resolution {
horizontal: o_vec[7].to_string(),
vertical: o_vec[9].replace(",", ""),
},
maximum: Resolution {
horizontal: o_vec[11].to_string(),
vertical: o_vec[13].replace(",", ""),
},
};
continue;
}
if o_vec.contains(&"connected") {
if active != Connector::default() {
connectors.push(active);
}
active = Connector::new();
active.set_name(o_vec[0].to_string());
active.set_status(o_vec[1].to_string());
let mut index = 2;
if o_vec[index] == "primary" {
active.set_primary(true);
} else {
active.set_primary(false);
index -= 1; }
index += 1;
let respos: Vec<&str> = o_vec[index].split(&['x', '+'][..]).collect();
active.set_current_resolution(Resolution {
horizontal: respos[0].to_string(),
vertical: respos[1].to_string(),
});
active.set_position(Position {
x: respos[2].to_string(),
y: respos[3].to_string(),
});
index += 1;
if o_vec[index].contains("(") {
active.set_orientation("normal".to_string());
} else {
active.set_orientation(o_vec[index].to_string());
index += 1;
}
let i7 = index + 7;
let filtered: Vec<String> =
o_vec[index..=i7].iter().map(|s| s.to_string()).collect();
let mut available_orientations: Vec<String> = Vec::new();
let mut i: usize = 0;
for ao in filtered {
let orientation: String = ao.replace(&['(', ')'][..], "");
if orientation == "axis" && i > 0 {
available_orientations[i - 1].push_str(" axis");
} else {
available_orientations.push(orientation);
i += 1;
}
}
active.set_available_orientations(available_orientations);
index += 8;
active.set_physical_dimensions(Dimensions {
x: o_vec[index].replace("mm", ""),
y: o_vec[index + 2].replace("mm", ""),
});
if o_vec.contains(&"disconnected") {
connectors.push(active);
active = Connector::default();
}
continue;
}
if o_vec.contains(&"disconnected") {
if active != Connector::default() {
connectors.push(active);
}
active = Connector::new();
active.set_name(o_vec[0].to_string());
active.set_status(o_vec[1].to_string());
continue;
}
if active != Connector::default() {
let mut outputs: Vec<Output> = active.output_info();
let mut rates: Vec<String> =
o_vec[1..].to_vec().iter().map(|s| s.to_string()).collect();
rates.retain(|r| r != "");
let resolution: Vec<&str> = o_vec[0].split('x').collect();
for r in &rates {
if r.contains('+') {
active.set_prefered_resolution(Resolution {
horizontal: resolution[0].to_string(),
vertical: resolution[1].to_string(),
});
active.set_prefered_refresh_rate(r.replace(&['+', '*'][..], ""));
}
if r.contains('*') {
active.set_current_refresh_rate(r.replace(&['+', '*'][..], ""));
}
}
rates = rates
.iter()
.map(|r| r.replace(&['+', '*'][..], ""))
.collect();
outputs.push(Output {
resolution: Resolution {
horizontal: resolution[0].to_string(),
vertical: resolution[1].to_string(),
},
rates,
});
active.set_output_info(outputs.to_vec());
}
}
if active != Connector::default() {
connectors.push(active);
}
Ok(connectors)
}
pub fn outputs(&self) -> Vec<String> {
self.outputs.to_vec()
}
pub fn connected_outputs(&self) -> Vec<String> {
self.connected_outputs.to_vec()
}
pub fn connectors(&self) -> Vec<Connector> {
self.connectors.to_vec()
}
pub fn screen(&self) -> Screen {
self.screen.clone()
}
pub fn get_connector(&self, connector: &str) -> Result<Connector, String> {
for c in &self.connectors {
if c.name == connector {
return Ok(c.clone());
}
}
Err("Not Found".to_string())
}
}