diff --git a/Cargo.lock b/Cargo.lock index aeaed65..630dcda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aho-corasick" version = "0.7.14" @@ -15,6 +17,12 @@ version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "fixscreen" version = "0.1.0" @@ -22,6 +30,23 @@ dependencies = [ "anyhow", "regex", "thiserror", + "xrandr", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", ] [[package]] @@ -30,12 +55,24 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -85,18 +122,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.21" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.21" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -117,3 +154,24 @@ name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "xrandr" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9faecbb5f354fe09959775758b6f7b6524ee80103a6c5c66a57112a2f707862" +dependencies = [ + "indexmap", + "thiserror", + "x11", +] diff --git a/Cargo.toml b/Cargo.toml index 9688af2..a714376 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ edition = "2018" thiserror = "1.0.21" anyhow = "1.0.33" regex = "1.4.2" +xrandr = "0.1.1" diff --git a/src/lib.rs b/src/lib.rs index 9157e97..7750ee6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,59 @@ use std::{ use regex::Regex; use thiserror::Error; +use xrandr::{XHandle, XrandrError}; + +pub fn swap_workspaces(cfg: &Config) -> Result>, CommandError> { + let monitors = XHandle::open()?.monitors()?; + + // TODO(wathiede): if I ever get two monitors with the same resolution, use EDID to find a + // better key. + let map: HashMap<_, _> = monitors + .iter() + .map(|m| ((m.width_px as usize, m.height_px as usize), m.name.clone())) + .collect(); + // TODO(wathiede): + // i3-msg commands to move them. + // Example: + // i3-msg '[workspace="trustee"]' move workspace to output DP-0 + Ok(cfg + .screens + .iter() + .map(|s| { + let key = match s.orientation { + Orientation::None | Orientation::Invert => { + (s.resolution.width, s.resolution.height) + } + Orientation::Left | Orientation::Right => (s.resolution.height, s.resolution.width), + }; + if let Some(m) = map.get(&key) { + println!("Moving {:?} to {}", s.workspaces, m); + run_move_workspace_cmd(&s.workspaces, m) + } else { + Vec::new() + } + }) + .flatten() + .collect()) +} +fn run_move_workspace_cmd( + workspaces: &[String], + monitor: &str, +) -> Vec> { + workspaces + .iter() + .map(|workspace| { + let s = format!(r#"'[workspace="{monitor}"]'"#); + let args = vec!["i3-msg", &s, "move", "workspace", "to", workspace]; + if cfg!(debug_assertions) { + Command::new("echo").args(args).output() + } else { + Command::new(&args[0]).args(&args[1..]).output() + } + }) + .map(|r| r.map_err(CommandError::from)) + .collect() +} #[derive(Error, Debug)] pub enum ParseError { @@ -16,6 +69,8 @@ pub enum ParseError { Io(#[from] io::Error), #[error("invalid data: {0}")] Parse(String), + #[error("xrandr: {0}")] + XrandrError(#[from] XrandrError), } // Map monitor name to DFP connection. @@ -43,20 +98,23 @@ pub enum CommandError { MissingMonitor(String), #[error("error executing command")] Io(#[from] io::Error), + #[error("xrandr error: {0}")] + XrandrError(#[from] XrandrError), } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Resolution { pub width: usize, pub height: usize, } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Offset { pub x: isize, pub y: isize, } +#[derive(Clone)] pub enum Orientation { None, Right, @@ -80,12 +138,14 @@ impl fmt::Debug for Orientation { } } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Screen { pub name: String, pub resolution: Resolution, pub offset: Offset, pub orientation: Orientation, + pub primary: bool, + pub workspaces: Vec, } impl Screen { @@ -119,16 +179,16 @@ impl Screen { )) /* Ok(format!("{connection}: {w}x{h} @{in_w}x{in_h} {x:+}{y:+} {{ForceCompositionPipeline=On, ViewPortIn={in_w}x{in_h}, ViewPortOut={w}x{h}+0+0, Rotation={rotation}}}", - connection=connection, - w=width, - h=height, - in_w=in_w, - in_h=in_h, - x=x, - y=y, - rotation=rotation, + connection=connection, + w=width, + h=height, + in_w=in_w, + in_h=in_h, + x=x, + y=y, + rotation=rotation, )) - */ + */ } } @@ -137,7 +197,7 @@ pub struct Config { pub screens: Vec, } -pub fn run_cmd(screen_mapping: &ScreenMapping, cfg: Config) -> Result { +pub fn run_cmd(screen_mapping: &ScreenMapping, cfg: &Config) -> Result { let args = build_cmd_args(screen_mapping, cfg)?; if cfg!(debug_assertions) { Ok(Command::new("echo").args(args).output()?) @@ -148,7 +208,7 @@ pub fn run_cmd(screen_mapping: &ScreenMapping, cfg: Config) -> Result Result, CommandError> { let metamode = cfg .screens diff --git a/src/main.rs b/src/main.rs index 13427fd..d383f52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ fn main() -> Result<()> { y: 1440 * 2 - 1920, }, orientation: Orientation::Left, + workspaces: vec!["distortion".to_string()], ..Default::default() }; let lg = Screen { @@ -29,6 +30,7 @@ fn main() -> Result<()> { x: 1 + dell.resolution.height as isize, y: 0, }, + workspaces: vec!["virtue".to_string()], ..Default::default() }; let lenovo = Screen { @@ -41,15 +43,24 @@ fn main() -> Result<()> { x: (dell.resolution.height + (lg.resolution.width - 2560) / 2) as isize, y: (lg.resolution.height) as isize, }, + primary: true, + workspaces: vec!["fraud".to_string(), "twilight".to_string()], ..Default::default() }; + let screens = vec![dell, lg, lenovo]; let cfg = Config { - screens: vec![dell, lg, lenovo], + screens: screens.clone(), }; let map = screen_mapping_from_xorg_log("/var/log/X.0.log")?; - let cmd = run_cmd(&map, cfg)?; + let cmd = run_cmd(&map, &cfg)?; println!("cmd {:#?}", cmd); + // TODO(wathiede): run xrandr --output $DPY --primary + // TODO(wathiede): i3-msg to move workspaces to proper places. + let res = swap_workspaces(&cfg)?; + for r in res { + println!("r = {:?}", r); + } Ok(()) }