diff --git a/src/lib.rs b/src/lib.rs index fefc106..df6b971 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,14 @@ use std::io; use std::collections::HashSet; use std::process::Command; use std::num::ParseIntError; +use std::str::FromStr; use std::string::FromUtf8Error; +/// Converts a resolution to a string. +pub fn resolution_to_string(res: (u32, u32)) -> String { + format!("{}x{}", res.0, res.1).to_owned() +} + /// The different kinds of errors that can happen. #[derive(Debug)] pub enum Error { @@ -17,6 +23,51 @@ pub enum Error { /// An error happened while parsing the resolution of a xrandr screen. ParseIntError(ParseIntError), + /// Not enough screens detected. + NoScreenDetected, + +} + +#[derive(Debug, Copy, Clone)] +/// The relative location of the second screen compared to the first one. +pub enum Side { + /// On the right of the main screen. + Right, + + /// On the left of the main screen. + Left, + + /// Above the main screen. + Above, + + /// Below the main screen. + Below, +} + +impl Side { + /// Returns the corresponding xrandr argument. + pub fn to_argument(&self) -> String { + match *self { + Side::Right => "--right-of", + Side::Left => "--left-of", + Side::Above => "--above", + Side::Below => "--below", + }.to_owned() + } +} + +impl FromStr for Side { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "left" => Ok(Side::Left), + "right" => Ok(Side::Right), + "above" => Ok(Side::Above), + "below" => Ok(Side::Below), + _ => Err(()), + } + } } impl From for Error { @@ -76,10 +127,10 @@ impl Screen { let mut output = None; for element in intersection { - if let Some(res) = output { - if element > &res { - output = Some(*element); - } + match output { + None => output = Some(*element), + Some(res) if &res < element => output = Some(*element), + _ => (), } } @@ -91,7 +142,7 @@ impl Screen { #[derive(Debug)] pub struct MultiScreen { /// All the contained screens. - pub(crate) screens: Vec, + screens: Vec, } impl MultiScreen { @@ -112,6 +163,8 @@ impl MultiScreen { continue; } + // This will match whether its connected or disconnected + // It tells us it is a new screen if line.contains("connected") { screens.push(Screen::from_line(line)); @@ -142,8 +195,21 @@ impl MultiScreen { Ok(MultiScreen { screens: screens }) } + /// Returns a pair of screen corresponding to the two main screens. + pub fn main_screens(&self) -> Option<(&Screen, &Screen)> { + if let Some(main) = self.screens.iter().filter(|x| x.primary).next() { + if let Some(second) = self.screens.iter().filter(|x| !x.primary && x.connected).next() { + Some((main, second)) + } else { + None + } + } else { + None + } + } + /// Returns the arguments of xrandr that disable all screens except the primary. - pub fn only_primary_args(&self) -> Vec<&str> { + pub fn only_primary_args(&self) -> Vec { let mut primary_found = false; let mut args = vec![]; @@ -151,13 +217,13 @@ impl MultiScreen { for screen in &self.screens { if ! screen.primary || primary_found { - args.push("--output"); - args.push(&screen.name); - args.push("--off"); + args.push("--output".to_owned()); + args.push(screen.name.clone()); + args.push("--off".to_owned()); } else { - args.push("--output"); - args.push(&screen.name); - args.push("--auto"); + args.push("--output".to_owned()); + args.push(screen.name.clone()); + args.push("--auto".to_owned()); primary_found = true; } @@ -167,9 +233,51 @@ impl MultiScreen { } /// Returns the arguments to duplicate the screens. - pub fn duplicate_args(&self) -> Vec<&str> { - panic!("Not implemented yet"); - vec![] + pub fn duplicate_args(&self) -> Option> { + if let Some((ref main, ref second)) = self.main_screens() { + if let Some(best_resolution) = main.best_common_resolution(second) { + + Some(vec![ + "--output".to_owned(), + main.name.clone(), + "--mode".to_owned(), + resolution_to_string(best_resolution), + + "--output".to_owned(), + second.name.clone(), + "--mode".to_owned(), + resolution_to_string(best_resolution), + "--same-as".to_owned(), + main.name.clone(), + ]) + + } else { + None + } + } else { + None + } + } + + /// Returns the arguments for two screens. + pub fn two_screen_args(&self, side: Side) -> Option> { + if let Some((ref main, ref second)) = self.main_screens() { + + Some(vec![ + "--output".to_owned(), + main.name.clone(), + "--auto".to_owned(), + + "--output".to_owned(), + second.name.clone(), + "--auto".to_owned(), + side.to_argument(), + main.name.clone(), + ]) + + } else { + None + } } /// Calls the xrandr program to disable all screens except the primary. @@ -179,11 +287,24 @@ impl MultiScreen { /// Calls the xrandr program to duplicate the screens. pub fn duplicate(&self) -> Result<(), Error> { - self.run_command(self.duplicate_args()) + if let Some(args) = self.duplicate_args() { + self.run_command(args) + } else { + Err(Error::NoScreenDetected) + } + } + + /// Calls the xrandr program to set two screens. + pub fn two_screens(&self, side: Side) -> Result<(), Error> { + if let Some(args) = self.two_screen_args(side){ + self.run_command(args) + } else { + Err(Error::NoScreenDetected) + } } /// Run an xrandr command with its args. - pub fn run_command(&self, args: Vec<&str>) -> Result<(), Error> { + pub fn run_command(&self, args: Vec) -> Result<(), Error> { Command::new("xrandr") .args(args) diff --git a/src/tv.rs b/src/tv.rs index 3a6e2b3..b8d2685 100644 --- a/src/tv.rs +++ b/src/tv.rs @@ -1,7 +1,7 @@ extern crate clap; extern crate tvrs; -use clap::{Arg, App, SubCommand}; +use clap::{Arg, App}; use tvrs::MultiScreen; fn main() { @@ -9,31 +9,33 @@ fn main() { let matches = App::new("tvrs") .author("Thomas Forgione ") .version("0.1.0") - .arg(Arg::with_name("disable") - .help("Disable all screens except the primary") - .conflicts_with_all(&["enable", "duplicate"])) - .arg(Arg::with_name("duplicate") - .help("Duplicate the primary screen on the second one") - .conflicts_with_all(&["enable", "disable"])) - .arg(Arg::with_name("enable") - .help("Enable the second screen") - .requires("enable") - .conflicts_with_all(&["disable", "duplicate"])) + .arg(Arg::with_name("action") + .help("Action to do") + .takes_value(true) + .value_name("ACTION") + .possible_values(&["disable", "duplicate", "enable"]) + .required(true)) .arg(Arg::with_name("position") .help("Specify the position of the second screen") - .possible_values(&["left", "right", "top", "bottom"])) + .possible_values(&["left", "right", "above", "below"]) + .required_if("action", "enable")) .get_matches(); let screens = MultiScreen::detect() .expect("An error happened while finding screens"); - if matches.is_present("disable") { - screens.only_primary() - .expect("An error happenned while executing the command"); - } else if matches.is_present("duplicate") { - panic!("Not implemented yet"); - } else if matches.is_present("enable") { - panic!("Not implemented yet"); - } + let result = match matches.value_of("action") { + Some("disable") => screens.only_primary(), + Some("duplicate") => screens.duplicate(), + Some("enable") => { + // Can never fail, clap will crash before this + let side = matches.value_of("position").unwrap().parse().unwrap(); + screens.two_screens(side) + }, + // Can never happen, clap will crash before this + _ => Ok(()) + }; + + result.expect("An error happenned while executing the command"); }