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

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