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:
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
|
||||
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(())
|
||||
}
|
||||
}
|
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