Compare commits

...

17 Commits

Author SHA1 Message Date
3207b7704b Latest monitor setup 2024-03-24 12:58:42 -07:00
5113e9d7a5 Remove debugging. 2023-06-23 17:10:51 -07:00
4429b6a6a6 Add LG monitor to the right center of main screens. 2023-06-23 17:10:02 -07:00
5a416f4552 Update i3 workspaces 2023-06-05 19:14:07 -07:00
fa892c9650 Latest monitor setup. 2023-06-05 19:09:10 -07:00
333119a00d Better error printing, fixed argument formatting. 2023-02-05 13:38:56 -08:00
72100921e6 WIP 2023-02-04 15:49:14 -08:00
5e90576c49 Update Dell orientation. 2023-02-04 15:48:28 -08:00
f230dcd701 Lint 2023-01-23 16:14:50 -08:00
6ae87393c7 Recenter bottom monitor and add small gap on top monitor. 2023-01-21 13:13:13 -08:00
4376dc55be Don't set so many parameters. It was upsetting i3. 2023-01-21 13:09:53 -08:00
61e9f4d9b5 Lower Dell monitor and make edges flush. 2023-01-21 12:00:22 -08:00
5efe275748 Add Dell monitor in rotated orientation. 2023-01-21 11:46:28 -08:00
4a5428f3a8 Remove Dell monitor that is no longer physically present. 2021-09-25 08:37:55 -07:00
2e9f3f2ea0 Unrotate dell monitor to reflect new physical reality.
Had some quirks with latest i3 + nvidia drivers, so unrotate for now.
2021-05-20 18:18:42 -07:00
da10975070 Update new physical orientation of Dell monitor. 2021-02-06 12:42:48 -08:00
3548e078ce Add nix and envrc setup. 2021-02-06 12:41:45 -08:00
4 changed files with 231 additions and 64 deletions

66
Cargo.lock generated
View File

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

View File

@@ -10,3 +10,4 @@ edition = "2018"
thiserror = "1.0.21"
anyhow = "1.0.33"
regex = "1.4.2"
xrandr = "0.1.1"

View File

@@ -1,12 +1,69 @@
use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use std::process::{Command, Output};
use std::{
collections::HashMap,
fmt,
fs::File,
io::{self, BufRead, BufReader},
path::Path,
process::{Command, Output},
};
use regex::Regex;
use thiserror::Error;
use xrandr::{XHandle, XrandrError};
pub fn swap_workspaces(cfg: &Config) -> Result<Vec<Result<Output, CommandError>>, 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<Result<Output, CommandError>> {
workspaces
.iter()
.map(|workspace| {
let s = format!(r#"[workspace="{workspace}"]"#);
let args = vec!["i3-msg", &s, "move", "workspace", "to", "output", monitor];
println!("{}", args.join(" "));
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 {
@@ -14,6 +71,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.
@@ -41,20 +100,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: usize,
pub y: usize,
pub x: isize,
pub y: isize,
}
#[derive(Clone)]
pub enum Orientation {
None,
Right,
@@ -78,19 +140,21 @@ 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<String>,
}
impl Screen {
fn metamode(&self, map: &ScreenMapping) -> Result<String, CommandError> {
let Resolution { width, height } = self.resolution;
let Offset { x, y } = self.offset;
let (in_w, in_h) = match self.orientation {
let (_in_w, _in_h) = match self.orientation {
Orientation::None | Orientation::Invert => (width, height),
Orientation::Right | Orientation::Left => (height, width),
};
@@ -106,16 +170,27 @@ impl Screen {
None => return Err(CommandError::MissingMonitor(self.name.to_string())),
};
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,
))
Ok(format!(
"{connection}: {w}x{h} {x:+}{y:+} {{ForceCompositionPipeline=On, Rotation={rotation}}}",
connection = connection,
w = width,
h = height,
x = x,
y = y,
rotation = rotation,
))
/*
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,
))
*/
}
}
@@ -124,7 +199,7 @@ pub struct Config {
pub screens: Vec<Screen>,
}
pub fn run_cmd(screen_mapping: &ScreenMapping, cfg: Config) -> Result<Output, CommandError> {
pub fn run_cmd(screen_mapping: &ScreenMapping, cfg: &Config) -> Result<Output, CommandError> {
let args = build_cmd_args(screen_mapping, cfg)?;
if cfg!(debug_assertions) {
Ok(Command::new("echo").args(args).output()?)
@@ -135,7 +210,7 @@ pub fn run_cmd(screen_mapping: &ScreenMapping, cfg: Config) -> Result<Output, Co
fn build_cmd_args(
screen_mapping: &ScreenMapping,
cfg: Config,
cfg: &Config,
) -> Result<Vec<String>, CommandError> {
let metamode = cfg
.screens
@@ -167,6 +242,7 @@ mod tests {
width: 1920,
height: 1200,
},
offset: Offset { x: 0, y: 0 },
orientation: Orientation::Right,
..Default::default()
},
@@ -201,14 +277,14 @@ mod tests {
assert_eq!(
build_cmd_args(&map, cfg).expect("failed build_cmd_args"),
vec![
"nvidia-settings",
"--assign",
"CurrentMetaMode=\
"nvidia-settings",
"--assign",
"CurrentMetaMode=\
DFP-5.8: 1920x1200 @1200x1920 +0+0 {ForceCompositionPipeline=On, ViewPortIn=1200x1920, ViewPortOut=1920x1200+0+0, Rotation=270}, \
DFP-0.1: 3440x1440 @3440x1440 +1200+0 {ForceCompositionPipeline=On, ViewPortIn=3440x1440, ViewPortOut=3440x1440+0+0, Rotation=0}, \
DFP-0.8: 2560x1440 @2560x1440 +4640+0 {ForceCompositionPipeline=On, ViewPortIn=2560x1440, ViewPortOut=2560x1440+0+0, Rotation=0}"
]
]
);
}

View File

@@ -1,43 +1,75 @@
use anyhow::Result;
use fixscreen::*;
fn main() -> Result<()> {
let dell43 = Screen {
name: ("DELL U4323QE".to_string()),
resolution: Resolution {
width: 3840,
height: 2160,
},
offset: Offset { x: 2560, y: 0 },
//orientation: Orientation::Left,
workspaces: vec!["fraud".to_string()],
..Default::default()
};
let lenovo = Screen {
name: ("Lenovo Group Limited P27h-20".to_string()),
resolution: Resolution {
width: 2560,
height: 1440,
},
offset: Offset {
x: 0,
y: ((dell43.resolution.height - 1440) / 2) as isize,
},
primary: true,
workspaces: vec!["distortion".to_string()],
..Default::default()
};
let lg = Screen {
name: ("LG Electronics 34UM95".to_string()),
resolution: Resolution {
width: 3440,
height: 1440,
},
offset: Offset {
// The '1 +' here creates a gap between the top monitor and side monitor. When all
// monitors have a gap, you can move the mouse between any combo. When the top monitor
// is flush with the left, the bottom monitor will have a hard edge that prevents the
// mouse from moving to the left monitor.
x: (lenovo.resolution.width + dell43.resolution.width) as isize,
y: ((dell43.resolution.height - 1440) / 2) as isize,
},
workspaces: vec!["virtue".to_string()],
..Default::default()
};
let map = screen_mapping_from_xorg_log("/var/log/X.0.log")?;
let lg_connected = std::env::args().len() > 1;
let mut screens = vec![dell43, lenovo];
if lg_connected {
screens.push(lg);
}
let cfg = Config {
screens: vec![
Screen {
name: ("DELL U2415".to_string()),
resolution: Resolution {
width: 1920,
height: 1200,
},
orientation: Orientation::Right,
..Default::default()
},
Screen {
name: ("LG Electronics 34UM95".to_string()),
resolution: Resolution {
width: 3440,
height: 1440,
},
offset: Offset { x: 1200, y: 0 },
..Default::default()
},
Screen {
name: ("Lenovo Group Limited P27h-20".to_string()),
resolution: Resolution {
width: 2560,
height: 1440,
},
offset: Offset { x: 4640, y: 0 },
..Default::default()
},
],
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
let res = swap_workspaces(&cfg)?;
for r in res {
match r {
Ok(r) => println!(
"Exit: {}\nStdout:\n{}\nStderr:\n{}",
r.status,
String::from_utf8_lossy(&r.stdout),
String::from_utf8_lossy(&r.stderr)
),
Err(e) => println!("Error: {}", e),
}
}
Ok(())
}