This commit is contained in:
Thomas Forgione 2018-09-26 16:58:39 +02:00
parent 55f46bcc50
commit 9e40498be6
No known key found for this signature in database
GPG Key ID: 203DAEA747F48F41
2 changed files with 160 additions and 37 deletions

View File

@ -2,8 +2,14 @@ use std::io;
use std::collections::HashSet; use std::collections::HashSet;
use std::process::Command; use std::process::Command;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::str::FromStr;
use std::string::FromUtf8Error; 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. /// The different kinds of errors that can happen.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -17,6 +23,51 @@ pub enum Error {
/// An error happened while parsing the resolution of a xrandr screen. /// An error happened while parsing the resolution of a xrandr screen.
ParseIntError(ParseIntError), 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<Side, Self::Err> {
match s {
"left" => Ok(Side::Left),
"right" => Ok(Side::Right),
"above" => Ok(Side::Above),
"below" => Ok(Side::Below),
_ => Err(()),
}
}
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
@ -76,10 +127,10 @@ impl Screen {
let mut output = None; let mut output = None;
for element in intersection { for element in intersection {
if let Some(res) = output { match output {
if element > &res { None => output = Some(*element),
output = Some(*element); Some(res) if &res < element => output = Some(*element),
} _ => (),
} }
} }
@ -91,7 +142,7 @@ impl Screen {
#[derive(Debug)] #[derive(Debug)]
pub struct MultiScreen { pub struct MultiScreen {
/// All the contained screens. /// All the contained screens.
pub(crate) screens: Vec<Screen>, screens: Vec<Screen>,
} }
impl MultiScreen { impl MultiScreen {
@ -112,6 +163,8 @@ impl MultiScreen {
continue; continue;
} }
// This will match whether its connected or disconnected
// It tells us it is a new screen
if line.contains("connected") { if line.contains("connected") {
screens.push(Screen::from_line(line)); screens.push(Screen::from_line(line));
@ -142,8 +195,21 @@ impl MultiScreen {
Ok(MultiScreen { screens: screens }) 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. /// 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<String> {
let mut primary_found = false; let mut primary_found = false;
let mut args = vec![]; let mut args = vec![];
@ -151,13 +217,13 @@ impl MultiScreen {
for screen in &self.screens { for screen in &self.screens {
if ! screen.primary || primary_found { if ! screen.primary || primary_found {
args.push("--output"); args.push("--output".to_owned());
args.push(&screen.name); args.push(screen.name.clone());
args.push("--off"); args.push("--off".to_owned());
} else { } else {
args.push("--output"); args.push("--output".to_owned());
args.push(&screen.name); args.push(screen.name.clone());
args.push("--auto"); args.push("--auto".to_owned());
primary_found = true; primary_found = true;
} }
@ -167,9 +233,51 @@ impl MultiScreen {
} }
/// Returns the arguments to duplicate the screens. /// Returns the arguments to duplicate the screens.
pub fn duplicate_args(&self) -> Vec<&str> { pub fn duplicate_args(&self) -> Option<Vec<String>> {
panic!("Not implemented yet"); if let Some((ref main, ref second)) = self.main_screens() {
vec![] 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<Vec<String>> {
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. /// 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. /// Calls the xrandr program to duplicate the screens.
pub fn duplicate(&self) -> Result<(), Error> { 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. /// Run an xrandr command with its args.
pub fn run_command(&self, args: Vec<&str>) -> Result<(), Error> { pub fn run_command(&self, args: Vec<String>) -> Result<(), Error> {
Command::new("xrandr") Command::new("xrandr")
.args(args) .args(args)

View File

@ -1,7 +1,7 @@
extern crate clap; extern crate clap;
extern crate tvrs; extern crate tvrs;
use clap::{Arg, App, SubCommand}; use clap::{Arg, App};
use tvrs::MultiScreen; use tvrs::MultiScreen;
fn main() { fn main() {
@ -9,31 +9,33 @@ fn main() {
let matches = App::new("tvrs") let matches = App::new("tvrs")
.author("Thomas Forgione <thomas@forgione.fr>") .author("Thomas Forgione <thomas@forgione.fr>")
.version("0.1.0") .version("0.1.0")
.arg(Arg::with_name("disable") .arg(Arg::with_name("action")
.help("Disable all screens except the primary") .help("Action to do")
.conflicts_with_all(&["enable", "duplicate"])) .takes_value(true)
.arg(Arg::with_name("duplicate") .value_name("ACTION")
.help("Duplicate the primary screen on the second one") .possible_values(&["disable", "duplicate", "enable"])
.conflicts_with_all(&["enable", "disable"])) .required(true))
.arg(Arg::with_name("enable")
.help("Enable the second screen")
.requires("enable")
.conflicts_with_all(&["disable", "duplicate"]))
.arg(Arg::with_name("position") .arg(Arg::with_name("position")
.help("Specify the position of the second screen") .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(); .get_matches();
let screens = MultiScreen::detect() let screens = MultiScreen::detect()
.expect("An error happened while finding screens"); .expect("An error happened while finding screens");
if matches.is_present("disable") { let result = match matches.value_of("action") {
screens.only_primary() Some("disable") => screens.only_primary(),
.expect("An error happenned while executing the command"); Some("duplicate") => screens.duplicate(),
} else if matches.is_present("duplicate") { Some("enable") => {
panic!("Not implemented yet"); // Can never fail, clap will crash before this
} else if matches.is_present("enable") { let side = matches.value_of("position").unwrap().parse().unwrap();
panic!("Not implemented yet"); screens.two_screens(side)
} },
// Can never happen, clap will crash before this
_ => Ok(())
};
result.expect("An error happenned while executing the command");
} }