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::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<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 {
@ -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<Screen>,
screens: Vec<Screen>,
}
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<String> {
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<Vec<String>> {
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<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.
@ -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<String>) -> Result<(), Error> {
Command::new("xrandr")
.args(args)

View File

@ -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 <thomas@forgione.fr>")
.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");
}