diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1e587ac..0000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -#Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 606eea8..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,182 +0,0 @@ -[[package]] -name = "bitflags" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "edid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "getopts" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "i3ipc" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "itoa" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "kanshi" -version = "0.2.1" -dependencies = [ - "edid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", - "i3ipc 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "xmltree 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libc" -version = "0.2.43" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libudev" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "memchr" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nom" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pkg-config" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ryu" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde_json" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-width" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "xml-rs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "xmltree" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" -"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" -"checksum edid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9bc713da4cdfc706399994b922141ca0bab58fbb316efa33d32ed16d3e34f3ca" -"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" -"checksum i3ipc 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0062d9e0f146755e3449fc3fd2ee02c4bedcb360f28d74ce1a05f8370da982f6" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe" -"checksum libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" -"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" -"checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" -"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" -"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" -"checksum serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "15c141fc7027dd265a47c090bf864cf62b42c4d228bbcf4e51a0c9e2b0d3f7ef" -"checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811" -"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" -"checksum xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" -"checksum xmltree 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff8eaee9d17062850f1e6163b509947969242990ee59a35801af437abe041e70" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 63f23e0..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "kanshi" -version = "0.2.1" -description = "Dynamic display configuration" -homepage = "https://github.com/emersion/kanshi" -repository = "https://github.com/emersion/kanshi" -readme = "README.md" -license = "MIT" -authors = ["emersion"] - -[dependencies] -edid = "0.2.0" -getopts = "0.2.18" -i3ipc = "0.8.4" -libudev = "0.2" -xmltree = "0.8.0" - -[dependencies.nom] -version = "3.2.1" -features = ["verbose-errors"] diff --git a/README.md b/README.md index e30fcd0..3ef5364 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,9 @@ Kanshi uses a configuration file and a list of available displays to choose the right settings for each display. It's useful if your window manager doesn't support multiple display configurations (e.g. i3/Sway). -For now, it only supports: - -* `sysfs` as backend -* `udev` as notifier (optional) -* Configuration file - * GNOME (`~/.config/monitors.xml`) - * Kanshi (see below) -* Sway as frontend - ## Usage ```sh -cargo install kanshi mkdir -p ~/.config/kanshi && touch ~/.config/kanshi/config kanshi ``` diff --git a/src/backend.rs b/src/backend.rs deleted file mode 100644 index 987c651..0000000 --- a/src/backend.rs +++ /dev/null @@ -1,77 +0,0 @@ -extern crate edid; - -use std::error::Error; -use std::fmt; -use std::fs::{File, read_dir}; -use std::io::prelude::*; - -#[derive(Debug)] -pub struct ConnectedOutput { - pub name: String, - pub edid: edid::EDID, -} - -impl ConnectedOutput { - pub fn vendor(&self) -> String { - self.edid.header.vendor[..].iter().collect::() - } - - pub fn product(&self) -> String { - self.edid.descriptors.iter() - .filter_map(|d| match d { - &edid::Descriptor::ProductName(ref s) => Some(s.to_string()), - _ => None, - }) - .nth(0) - .unwrap_or(format!("0x{:X}", self.edid.header.product)) - } - - pub fn serial(&self) -> String { - self.edid.descriptors.iter() - .filter_map(|d| match d { - &edid::Descriptor::SerialNumber(ref s) => Some(s.to_string()), - _ => None, - }) - .nth(0) - .unwrap_or(format!("0x{:X}", self.edid.header.serial)) - } -} - -impl fmt::Display for ConnectedOutput { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "output {} vendor {} product {} serial {}", self.name, self.vendor(), self.product(), self.serial()) - } -} - -pub trait Backend { - fn list_outputs(&self) -> Result, Box>; -} - -pub struct SysFsBackend; - -const OUTPUT_PREFIX: &str = "card0-"; - -impl Backend for SysFsBackend { - fn list_outputs(&self) -> Result, Box> { - let outputs = read_dir("/sys/class/drm")? - .map(|r| r.unwrap()) - .filter(|e| e.file_name().to_str().unwrap().starts_with(OUTPUT_PREFIX)) - .filter(|e| { - let mut status = String::new(); - File::open(e.path().join("status")).unwrap().read_to_string(&mut status).unwrap(); - status.trim() == "connected" - }) - .map(|e| { - let name = e.file_name().to_str().unwrap().trim_left_matches(OUTPUT_PREFIX).to_string(); - - let mut buf = Vec::new(); - File::open(e.path().join("edid")).unwrap().read_to_end(&mut buf).unwrap(); - let (_, edid) = edid::parse(&buf).unwrap(); - - ConnectedOutput{name, edid} - }) - .collect::>(); - - Ok(outputs) - } -} diff --git a/src/frontend.rs b/src/frontend.rs deleted file mode 100644 index f2bafa8..0000000 --- a/src/frontend.rs +++ /dev/null @@ -1,95 +0,0 @@ -extern crate i3ipc; - -use std::error::Error; -use std::io; -use std::io::Write; - -use getopts; -use self::i3ipc::I3Connection; - -use backend::ConnectedOutput; -use store::SavedOutput; -use store::Transform; - -#[derive(Debug)] -pub struct MatchedOutput<'a> { - pub connected: &'a ConnectedOutput, - pub saved: &'a SavedOutput, -} - -pub trait Frontend { - fn apply_configuration<'a>(&self, Option<&'a [MatchedOutput<'a>]>) -> Result<(), Box>; -} - -pub struct SwayFrontend { - primary_workspace: Option, -} - -impl SwayFrontend { - pub fn new(opts: getopts::Matches) -> SwayFrontend { - SwayFrontend{ - primary_workspace: opts.opt_str("primary-workspace"), - } - } - - fn get_commands<'a>(&self, config: Option<&'a [MatchedOutput<'a>]>) -> Vec { - if let Some(config) = config { - let mut cmds = Vec::with_capacity(config.len()); - for MatchedOutput{connected, saved} in config { - if saved.enabled { - let mut l = format!("output {} enable", &connected.name); - l += &format!(" position {} {}", saved.x, saved.y); - if saved.width > 0 && saved.height > 0 { - l += &format!(" resolution {}x{}", saved.width, saved.height); - } - l += &format!(" transform {}", match saved.transform { - Transform::Rotation(90) => "90", - Transform::Rotation(180) => "180", - Transform::Rotation(270) => "270", - Transform::FlippedRotation(0) => "flipped", - Transform::FlippedRotation(90) => "flipped-90", - Transform::FlippedRotation(180) => "flipped-180", - Transform::FlippedRotation(270) => "flipped-270", - _ => "normal" - }); - if saved.scale > 0. { - l += &format!(" scale {}", saved.scale); - } - cmds.push(l); - - if saved.primary { - if let Some(ref workspace) = self.primary_workspace { - cmds.push(format!("workspace {} output {}", workspace, &connected.name)); - } - } - } else { - cmds.push(format!("output {} disable", &connected.name)); - } - } - cmds - } else { - Vec::new() - } - } - - fn print_configuration<'a>(&self, config: Option<&'a [MatchedOutput<'a>]>) -> Result<(), Box> { - let cmds = self.get_commands(config); - let mut w = io::stdout(); - for cmd in cmds { - let l = cmd + "\n"; - w.write(l.as_bytes())?; - } - Ok(()) - } -} - -impl Frontend for SwayFrontend { - fn apply_configuration<'a>(&self, config: Option<&'a [MatchedOutput<'a>]>) -> Result<(), Box> { - let cmds = self.get_commands(config); - let mut conn = I3Connection::connect()?; - for cmd in cmds { - conn.run_command(&cmd)?; - } - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 511ccf2..0000000 --- a/src/main.rs +++ /dev/null @@ -1,220 +0,0 @@ -extern crate edid; -extern crate getopts; -#[macro_use] -extern crate nom; - -mod backend; -mod store; -mod frontend; -mod notifier; - -use std::io::Write; -use std::env; - -use getopts::Options; - -use backend::{ConnectedOutput, Backend, SysFsBackend}; -use store::{SavedOutput, SavedConfiguration, Store, GnomeStore, KanshiStore}; -use frontend::{MatchedOutput, Frontend, SwayFrontend}; -use notifier::{Notifier, UdevNotifier}; -use std::sync::mpsc::channel; -use std::process::Command; - -#[derive(Debug)] -pub struct MatchedConfiguration<'a> { - pub outputs: Vec>, - pub saved: &'a SavedConfiguration, -} - -fn connector_type(name: &str) -> Option { - let name = name.to_lowercase(); - - [ - "VGA", "Unknown", "DVI", "Composite", "SVIDEO", "LVDS", "Component", "DIN", "DP", "HDMI", - "TV", "eDP", "Virtual", "DSI", - ] - .iter() - .find(|t| name.starts_with(t.to_lowercase().as_str())) - .map(|s| s.to_string()) -} - -impl PartialEq for ConnectedOutput { - fn eq(&self, other: &SavedOutput) -> bool { - if other.name != "" { - if let Some(t) = connector_type(&self.name) { - if let Some(other_t) = connector_type(&other.name) { - if t != other_t { - return false; - } - } else { - return false; - } - } - } - if other.vendor != "" { - if self.vendor() != other.vendor { - return false; - } - } - if other.product.starts_with("0x") { - let other_product = u16::from_str_radix(other.product.trim_left_matches("0x"), 16).unwrap(); - if other_product != self.edid.header.product { - return false; - } - } else if other.product != "" { - if self.product() != other.product { - return false; - } - } - if other.serial.starts_with("0x") { - let other_serial = u32::from_str_radix(other.serial.trim_left_matches("0x"), 16).unwrap(); - if other_serial != self.edid.header.serial { - return false; - } - } else if other.serial != "" { - if self.serial() != other.serial { - return false; - } - } - return true; - } -} - -fn print_usage(program: &str, opts: Options) { - let brief = format!("Usage: {} [options]", program); - print!("{}", opts.usage(&brief)); -} - -fn main() { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - - let mut opts = Options::new(); - - opts - .optopt("b", "backend", "set the backend (sysfs)", "") - .optopt("s", "store", "set the store (gnome, kanshi)", "") - .optopt("f", "frontend", "set the frontend (sway)", "") - .optopt("n", "notifier", "set the notifier (udev)", "") - .optopt("", "primary-workspace", "set the primary workspace name (sway)", "") - .optflag("h", "help", "print this help menu"); - - let opts_matches = opts.parse(&args[1..]).unwrap(); - - if opts_matches.opt_present("h") { - print_usage(&program, opts); - return; - } - - let mut stderr = std::io::stderr(); - - let backend: Box = match opts_matches.opt_str("backend").as_ref().map(String::as_ref) { - None | Some("sysfs") => Box::new(SysFsBackend{}), - _ => panic!("Unknown backend"), - }; - - let store: Box = match opts_matches.opt_str("store").as_ref().map(String::as_ref) { - Some("gnome") => Box::new(GnomeStore{}), - None | Some("kanshi") => Box::new(KanshiStore{}), - _ => panic!("Unknown store"), - }; - - let notifier: Option> = match opts_matches.opt_str("notifier").as_ref().map(String::as_ref) { - None | Some("udev") => Some(Box::new(UdevNotifier{})), - Some("none") => None, - _ => panic!("Unknown notifier"), - }; - - let frontend: Box = match opts_matches.opt_str("frontend").as_ref().map(String::as_ref) { - None | Some("sway") => Box::new(SwayFrontend::new(opts_matches)), - _ => panic!("Unknown frontend"), - }; - - let rx = notifier.map(|notifier| { - let (tx, rx) = channel(); - notifier.notify(tx).unwrap(); - rx - }); - - loop { - let connected_outputs = match backend.list_outputs() { - Ok(c) => c, - Err(err) => { - writeln!(&mut stderr, "Error: cannot list connected monitors: {}", err).unwrap(); - std::process::exit(1); - }, - }; - - writeln!(&mut stderr, "Connected outputs:").unwrap(); - for o in &connected_outputs { - writeln!(&mut stderr, "{}", o).unwrap(); - } - - let configurations = match store.list_configurations() { - Ok(c) => c, - Err(err) => { - writeln!(&mut stderr, "Error: cannot list saved monitor configurations: {}", err).unwrap(); - std::process::exit(1); - }, - }; - - //writeln!(&mut stderr, "Saved configurations: {:?}", configurations).unwrap(); - - let connected_outputs = &connected_outputs; - let configuration = configurations.iter() - .filter_map(|config| { - let n_saved = config.outputs.len(); - if n_saved != connected_outputs.len() { - return None; - } - - let matched = config.outputs.iter() - .filter_map(|saved| { - connected_outputs.iter() - .find(|connected| **connected == *saved) - .map(|connected| MatchedOutput{connected, saved}) - }) - .collect::>(); - - if n_saved != matched.len() { - return None; - } - - Some(MatchedConfiguration{outputs: matched, saved: config}) - }) - .nth(0); - - writeln!(&mut stderr, "Matching configuration: {:?}", &configuration).unwrap(); - - { - let outputs = configuration.as_ref().map(|config| config.outputs.as_ref()); - match frontend.apply_configuration(outputs) { - Ok(()) => (), - Err(err) => { - writeln!(&mut stderr, "Error: cannot apply configuration: {}", err).unwrap(); - std::process::exit(1); - }, - }; - } - - match configuration.map(|config| &config.saved.exec) { - Some(exec) => { - for e in exec { - let cmd = e.get(0).unwrap(); - let args = e.get(1..).unwrap(); - writeln!(&mut stderr, "Executing command: {:?}", &cmd).unwrap(); - Command::new(cmd).args(args).spawn().unwrap(); - } - }, - None => (), - } - - match rx { - Some(ref rx) => { - writeln!(&mut stderr, "Waiting for output changes...").unwrap(); - rx.recv().unwrap(); - } - None => break - } - } -} diff --git a/src/notifier.rs b/src/notifier.rs deleted file mode 100644 index 6bee4e0..0000000 --- a/src/notifier.rs +++ /dev/null @@ -1,39 +0,0 @@ -extern crate libudev; - -use std::error::Error; -use std::sync::mpsc::Sender; -use std::thread; -use std::time::Duration; - -pub trait Notifier { - // TODO: use blocking I/O instead - fn notify(&self, tx: Sender<()>) -> Result<(), Box>; -} - -pub struct UdevNotifier {} - -impl Notifier for UdevNotifier { - fn notify(&self, tx: Sender<()>) -> Result<(), Box> { - thread::spawn(move || { - let ctx = libudev::Context::new().unwrap(); - let mut mon = libudev::Monitor::new(&ctx).unwrap(); - mon.match_subsystem("drm").unwrap(); - let mut socket = mon.listen().unwrap(); - - loop { - let _ = match socket.receive_event() { - Some(evt) => evt, - None => { - // TODO: poll socket instead - thread::sleep(Duration::from_millis(1000)); - continue; - } - }; - - tx.send(()).unwrap(); - } - }); - - Ok(()) - } -} diff --git a/src/store.rs b/src/store.rs deleted file mode 100644 index 1f5c947..0000000 --- a/src/store.rs +++ /dev/null @@ -1,329 +0,0 @@ -extern crate nom; -extern crate xmltree; - -use std::env; -use std::error::Error; -use std::fs::File; -use std::io::prelude::*; -use std::path::PathBuf; -use std::str; -use std::str::FromStr; - -#[derive(Debug)] -pub enum Transform { - Rotation(i32), - FlippedRotation(i32) -} -impl Default for Transform { - fn default() -> Transform { - Transform::Rotation(0) - } -} -impl Transform { - fn from_str(s: &str) -> Transform { - match s { - "90" => Transform::Rotation(90), - "180" => Transform::Rotation(180), - "270" => Transform::Rotation(270), - "flipped" => Transform::FlippedRotation(0), - "flipped-90" => Transform::FlippedRotation(90), - "flipped-180" => Transform::FlippedRotation(180), - "flipped-270" => Transform::FlippedRotation(270), - _ => Transform::Rotation(0) - } - } -} - -#[derive(Debug, Default)] -pub struct SavedOutput { - pub name: String, - pub vendor: String, - pub product: String, - pub serial: String, - - pub enabled: bool, - pub width: i32, - pub height: i32, - pub rate: f32, - pub x: i32, - pub y: i32, - pub transform: Transform, - //pub reflect_x: bool, - //pub reflect_y: bool, - pub primary: bool, - //pub presentation: bool, - //pub underscanning: bool, - pub scale: f32, -} - -#[derive(Debug, Default)] -pub struct SavedConfiguration { - pub outputs: Vec, - pub exec: Vec>, -} - -pub trait Store { - fn list_configurations(&self) -> Result, Box>; -} - -fn xdg_config_home() -> PathBuf { - env::var("XDG_CONFIG_HOME") - .map(PathBuf::from) - .unwrap_or(env::home_dir().unwrap().join(".config")) -} - -fn parse_bool(s: &str) -> bool { - s == "yes" -} - -pub struct GnomeStore; - -impl Store for GnomeStore { - fn list_configurations(&self) -> Result, Box> { - let monitors_path = xdg_config_home().join("monitors.xml"); - - let monitors = xmltree::Element::parse(File::open(&monitors_path)?).unwrap(); - let configurations = monitors.children.iter() - .filter(|e| e.name == "configuration") - .map(|e| { - // TODO: support - - let outputs = e.children.iter() - .filter(|e| e.name == "output") - .map(|e| { - let mut o = SavedOutput{ - name: e.attributes.get("name").unwrap().to_owned(), - vendor: e.get_child("vendor").unwrap().text.as_ref().unwrap().to_owned(), - product: e.get_child("product").unwrap().text.as_ref().unwrap().to_owned(), - serial: e.get_child("serial").unwrap().text.as_ref().unwrap().to_owned(), - ..SavedOutput::default() - }; - - if let Some(c) = e.get_child("width") { - o.width = c.text.as_ref().unwrap().parse::().unwrap() - } - if let Some(c) = e.get_child("height") { - o.height = c.text.as_ref().unwrap().parse::().unwrap() - } - if let Some(c) = e.get_child("rate") { - o.rate = c.text.as_ref().unwrap().parse::().unwrap() - } - if let Some(c) = e.get_child("x") { - o.x = c.text.as_ref().unwrap().parse::().unwrap() - } - if let Some(c) = e.get_child("y") { - o.y = c.text.as_ref().unwrap().parse::().unwrap() - } - if let Some(c) = e.get_child("rotation") { - match c.text.as_ref().unwrap().trim() { - "right" => o.transform = Transform::Rotation(90), - "inverted" => o.transform = Transform::Rotation(180), - "left" => o.transform = Transform::Rotation(270), - _ => o.transform = Transform::Rotation(0), - }; - } - if let Some(c) = e.get_child("primary") { - o.primary = parse_bool(c.text.as_ref().unwrap()) - } - - o.enabled = o.width != 0 && o.height != 0; - - o - }) - .collect::>(); - - SavedConfiguration{outputs, exec: Vec::new()} - }) - .collect::>(); - - Ok(configurations) - } -} - -named!(parse_space, eat_separator!(&b" \t"[..])); - -named!(parse_string<&[u8], String>, map_res!( - map_res!( - is_not_s!(" \t\r\n"), - str::from_utf8 - ), - FromStr::from_str -)); - -enum OutputArg { - Vendor(String), - Product(String), - Serial(String), - - Disable, - Resolution(i32, i32), - Position(i32, i32), - Transform(Transform), - Scale(f32), -} - -fn parse_output_with_args(name: String, args: Vec) -> SavedOutput { - let mut o = SavedOutput{name, enabled: true, ..SavedOutput::default()}; - - for arg in args { - match arg { - OutputArg::Vendor(v) => o.vendor = v, - OutputArg::Product(p) => o.product = p, - OutputArg::Serial(s) => o.serial = s, - OutputArg::Disable => o.enabled = false, - OutputArg::Resolution(w, h) => { - o.width = w; - o.height = h; - }, - OutputArg::Position(x, y) => { - o.x = x; - o.y = y; - }, - OutputArg::Transform(t) => o.transform = t, - OutputArg::Scale(f) => o.scale = f, - } - } - - o -} - -named!(parse_vendor<&[u8], OutputArg>, do_parse!( - tag!("vendor") - >> parse_space - >> v: parse_string - >> (OutputArg::Vendor(v)) -)); - -named!(parse_product<&[u8], OutputArg>, do_parse!( - tag!("product") - >> parse_space - >> p: parse_string - >> (OutputArg::Product(p)) -)); - -named!(parse_serial<&[u8], OutputArg>, do_parse!( - tag!("serial") - >> parse_space - >> s: parse_string - >> (OutputArg::Serial(s)) -)); - -named!(parse_disable<&[u8], OutputArg>, do_parse!(tag!("disable") >> (OutputArg::Disable))); - -named!(parse_i32<&[u8], i32>, map_res!( - map_res!( - nom::digit, - str::from_utf8 - ), - i32::from_str -)); - -named!(parse_resolution<&[u8], OutputArg>, do_parse!( - tag!("resolution") - >> parse_space - >> w: parse_i32 - >> tag!("x") - >> h: parse_i32 - >> (OutputArg::Resolution(w, h)) -)); - -named!(parse_position<&[u8], OutputArg>, do_parse!( - tag!("position") - >> parse_space - >> x: parse_i32 - >> tag!(",") - >> y: parse_i32 - >> (OutputArg::Position(x, y)) -)); - -named!(parse_transform<&[u8], OutputArg>, do_parse!( - tag!("transform") - >> parse_space - >> t: parse_string - >> (OutputArg::Transform(Transform::from_str(&t))) -)); - -named!(parse_f32<&[u8], f32>, ws!(nom::float)); - -named!(parse_scale<&[u8], OutputArg>, do_parse!( - tag!("scale") - >> parse_space - >> f: parse_f32 - >> (OutputArg::Scale(f)) -)); - -named!(parse_output_arg<&[u8], OutputArg>, alt!( - parse_vendor | parse_product | parse_serial | - parse_disable | parse_resolution | parse_position | parse_transform | parse_scale -)); - -enum ConfigurationArg { - Output(SavedOutput), - Exec(Vec), -} - -fn parse_configuration_with_args(args: Vec) -> SavedConfiguration { - let mut c = SavedConfiguration::default(); - - for arg in args { - match arg { - ConfigurationArg::Output(o) => c.outputs.push(o), - ConfigurationArg::Exec(e) => c.exec.push(e), - } - } - - c -} - -named!(parse_output_name<&[u8], String>, map!( - parse_string, - |s| { - if s == "*" { - String::from("") - } else { - s - } - } -)); - -named!(parse_output<&[u8], ConfigurationArg>, do_parse!( - tag!("output") - >> parse_space - >> name: parse_output_name - >> args: many0!(preceded!(parse_space, parse_output_arg)) - >> (ConfigurationArg::Output(parse_output_with_args(name, args))) -)); - -named!(parse_exec<&[u8], ConfigurationArg>, do_parse!( - tag!("exec") - >> cmd: many1!(preceded!(parse_space, parse_string)) - >> (ConfigurationArg::Exec(cmd)) -)); - -named!(parse_configuration_arg<&[u8], ConfigurationArg>, alt!(parse_output | parse_exec)); - -named!(parse_configuration_args<&[u8], Vec>, delimited!( - tag!("{"), - many0!(ws!(parse_configuration_arg)), - tag!("}") -)); - -named!(parse_configuration<&[u8], SavedConfiguration>, map!( - parse_configuration_args, parse_configuration_with_args -)); - -named!(parse_configuration_list<&[u8], Vec>, many0!(ws!(parse_configuration))); - -pub struct KanshiStore; - -impl Store for KanshiStore { - fn list_configurations(&self) -> Result, Box> { - let config_path = xdg_config_home().join("kanshi").join("config"); - - let mut buf = Vec::new(); - File::open(config_path).unwrap().read_to_end(&mut buf).unwrap(); - - let (_, config) = parse_configuration_list(&buf).unwrap(); - Ok(config) - } -}