mirror of
https://github.com/emersion/kanshi
synced 2024-11-26 14:03:46 +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:
parent
970267e400
commit
c1f6c85052
10
.gitignore
vendored
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
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"
|
|
20
Cargo.toml
20
Cargo.toml
@ -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"]
|
|
10
README.md
10
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
|
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(())
|
|
||||||
}
|
|
||||||
}
|
|
220
src/main.rs
220
src/main.rs
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
329
src/store.rs
329
src/store.rs
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user