1
0
mirror of https://github.com/emersion/kanshi synced 2024-11-26 05:55:27 +01:00

To the trash it goes

Remove all Rust code, it will be completely replaced with a C version.

* Rust as a language is too complicated
* New Rust releases break builds
* Rust makes it difficult for new contributors to jump in
This commit is contained in:
Simon Ser 2019-05-30 13:10:36 +03:00
parent 970267e400
commit c1f6c85052
9 changed files with 0 additions and 982 deletions

10
.gitignore vendored

@ -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

182
Cargo.lock generated

@ -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"

@ -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"]

@ -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 right settings for each display. It's useful if your window manager doesn't
support multiple display configurations (e.g. i3/Sway). 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 ## Usage
```sh ```sh
cargo install kanshi
mkdir -p ~/.config/kanshi && touch ~/.config/kanshi/config mkdir -p ~/.config/kanshi && touch ~/.config/kanshi/config
kanshi kanshi
``` ```

@ -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::<String>()
}
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<Vec<ConnectedOutput>, Box<Error>>;
}
pub struct SysFsBackend;
const OUTPUT_PREFIX: &str = "card0-";
impl Backend for SysFsBackend {
fn list_outputs(&self) -> Result<Vec<ConnectedOutput>, Box<Error>> {
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::<Vec<_>>();
Ok(outputs)
}
}

@ -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<Error>>;
}
pub struct SwayFrontend {
primary_workspace: Option<String>,
}
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<String> {
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<Error>> {
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<Error>> {
let cmds = self.get_commands(config);
let mut conn = I3Connection::connect()?;
for cmd in cmds {
conn.run_command(&cmd)?;
}
Ok(())
}
}

@ -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<MatchedOutput<'a>>,
pub saved: &'a SavedConfiguration,
}
fn connector_type(name: &str) -> Option<String> {
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<SavedOutput> 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<String> = env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts
.optopt("b", "backend", "set the backend (sysfs)", "<backend>")
.optopt("s", "store", "set the store (gnome, kanshi)", "<store>")
.optopt("f", "frontend", "set the frontend (sway)", "<frontend>")
.optopt("n", "notifier", "set the notifier (udev)", "<notifier>")
.optopt("", "primary-workspace", "set the primary workspace name (sway)", "<workspace>")
.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<Backend> = match opts_matches.opt_str("backend").as_ref().map(String::as_ref) {
None | Some("sysfs") => Box::new(SysFsBackend{}),
_ => panic!("Unknown backend"),
};
let store: Box<Store> = 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<Box<Notifier>> = 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<Frontend> = 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::<Vec<_>>();
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
}
}
}

@ -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<Error>>;
}
pub struct UdevNotifier {}
impl Notifier for UdevNotifier {
fn notify(&self, tx: Sender<()>) -> Result<(), Box<Error>> {
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(())
}
}

@ -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<SavedOutput>,
pub exec: Vec<Vec<String>>,
}
pub trait Store {
fn list_configurations(&self) -> Result<Vec<SavedConfiguration>, Box<Error>>;
}
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<Vec<SavedConfiguration>, Box<Error>> {
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: <clone> 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::<i32>().unwrap()
}
if let Some(c) = e.get_child("height") {
o.height = c.text.as_ref().unwrap().parse::<i32>().unwrap()
}
if let Some(c) = e.get_child("rate") {
o.rate = c.text.as_ref().unwrap().parse::<f32>().unwrap()
}
if let Some(c) = e.get_child("x") {
o.x = c.text.as_ref().unwrap().parse::<i32>().unwrap()
}
if let Some(c) = e.get_child("y") {
o.y = c.text.as_ref().unwrap().parse::<i32>().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::<Vec<_>>();
SavedConfiguration{outputs, exec: Vec::new()}
})
.collect::<Vec<_>>();
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<OutputArg>) -> 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<String>),
}
fn parse_configuration_with_args(args: Vec<ConfigurationArg>) -> 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<ConfigurationArg>>, 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<SavedConfiguration>>, many0!(ws!(parse_configuration)));
pub struct KanshiStore;
impl Store for KanshiStore {
fn list_configurations(&self) -> Result<Vec<SavedConfiguration>, Box<Error>> {
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)
}
}