Compare commits

..

2 Commits

Author SHA1 Message Date
dd2c80224f docs and renames 2024-12-12 03:21:05 +00:00
ea5ae5968a use filter().map() 2024-12-12 02:39:54 +00:00

View File

@@ -5,30 +5,42 @@ use std::env;
use std::process::Command; use std::process::Command;
use std::{fs, str}; use std::{fs, str};
// parse the config to this /// Configuration for swayout.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct Rules { struct Config {
/// Monitor name is independent of output name. It can be anything.
monitors: HashMap<String, Monitor>, monitors: HashMap<String, Monitor>,
// Map of layout name to : map of monitor name to output config /// Definition of layouts: a map of the layout name to the outputs.
/// The outputs is a map of the monitor name (in `monitors`) to the configuration
/// of that monitor for this layout.
/// Available outputs that do not match a monitor in the map are disabled.
layouts: HashMap<String, HashMap<String, OutputConfig>>, layouts: HashMap<String, HashMap<String, OutputConfig>>,
} }
/// Defines a monitor by make, model, and serial.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct Monitor { struct Monitor {
make: String, make: String,
model: String, model: String,
serial: String, serial: String,
} }
/// Configuration for an enabled output.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct OutputConfig { struct OutputConfig {
/// mode. See man 5 sway-output
mode: String, mode: String,
/// scale. See man 5 sway-output
scale: String, scale: String,
/// x value for position. See man 5 sway-output
x: u16, x: u16,
/// y value for position. See man 5 sway-output
y: u16, y: u16,
/// transform. See man 5 sway-output
transform: String, transform: String,
} }
// parse output of swaymsg -t get_outputs to this /// An output, as returned by `swaymsg -t get_outputs`.
struct Output { struct Output {
/// output name, according to sway
name: String, name: String,
make: String, make: String,
model: String, model: String,
@@ -48,9 +60,8 @@ fn main() {
} }
} }
/// Get all outputs currently available /// Get all outputs currently available, according to `swaymsg -t get_outputs`.
fn get_outputs() -> Vec<Output> { fn get_outputs() -> Vec<Output> {
// call `swaymsg -t get_outputs` to get outputs
let output = Command::new("swaymsg") let output = Command::new("swaymsg")
.arg("-t") .arg("-t")
.arg("get_outputs") .arg("get_outputs")
@@ -82,7 +93,7 @@ fn get_outputs() -> Vec<Output> {
} }
/// Parse the mode from the output JSON into a string suitable for the mode param for sway-output. /// Parse the mode from the output JSON into a string suitable for the mode param for sway-output.
/// This will go into OutputConfig.mode /// This will go into `OutputConfig.mode`.
fn get_mode(output: &Value) -> String { fn get_mode(output: &Value) -> String {
if let Value::Array(modes) = &output["modes"] { if let Value::Array(modes) = &output["modes"] {
if let Some(mode) = modes.first() { if let Some(mode) = modes.first() {
@@ -101,15 +112,16 @@ fn get_mode(output: &Value) -> String {
} }
} }
/// Get the rules from ~/.config/swayout/config.toml /// Get a `Config` from ~/.config/swayout/config.toml.
fn get_rules() -> Rules { /// (It really uses XDG config dirs.)
fn get_config() -> Config {
let xdg_dirs = xdg::BaseDirectories::with_prefix("swayout").unwrap(); let xdg_dirs = xdg::BaseDirectories::with_prefix("swayout").unwrap();
if let Some(path) = xdg_dirs.find_config_file("config.toml") { if let Some(path) = xdg_dirs.find_config_file("config.toml") {
let toml = fs::read_to_string(path).expect("error reading file"); let toml = fs::read_to_string(path).expect("error reading file");
toml::from_str(&toml).expect("toml parse error") toml::from_str(&toml).expect("toml parse error")
} else { } else {
// no config files, use an empty rules // no config files, use an empty rules
Rules { Config {
monitors: HashMap::new(), monitors: HashMap::new(),
layouts: HashMap::new(), layouts: HashMap::new(),
} }
@@ -118,34 +130,32 @@ fn get_rules() -> Rules {
/// Determine the available layout names. /// Determine the available layout names.
/// Return one layout for each layout defined in the configuration file /// Return one layout for each layout defined in the configuration file
/// (for which all outputs are available), /// for which all outputs are available,
/// one for each monitor defined in the configuration file /// one for each monitor defined in the configuration file (for using just that monitor),
/// (for using just that monitor),
/// and one for each available output that is not a configured monitor (for using just that output). /// and one for each available output that is not a configured monitor (for using just that output).
fn get_layouts() -> Vec<String> { fn get_layouts() -> Vec<String> {
let available_outputs = get_outputs(); let available_outputs = get_outputs();
let rules = get_rules(); let rules = get_config();
let mut layout_names: Vec<String> = Vec::new(); let mut layout_names: Vec<String> = Vec::new();
// Get the names of monitors that are available (that match a current output) // Get the names of monitors that are available (that match an available output)
let mut available_monitor_names: Vec<String> = Vec::new(); let available_monitor_names:Vec<&String> = rules.monitors.iter()
// TODO do this with iterators .filter(|(_monitor_name,monitor)|
rules.monitors.iter().for_each(|(monitor_name, monitor)| { available_outputs.iter().any(|output|
if available_outputs.iter().any(|output| { monitor.make == output.make
monitor.make == output.make && monitor.model == output.model
&& monitor.model == output.model && output.serial == monitor.serial
&& output.serial == monitor.serial )
}) { )
available_monitor_names.push(String::from(monitor_name)); .map(|(monitor_name,_monitor)| monitor_name)
} .collect();
});
// Add each layout defined in the config file for which all outputs are available // Add each layout defined in the config file for which all outputs are available
rules.layouts.iter().for_each(|(layout_name, layout)| { rules.layouts.iter().for_each(|(layout_name, layout)| {
if layout if layout
.iter() .iter()
.all(|(output_name, _output)| available_monitor_names.contains(output_name)) .all(|(output_name, _output)| available_monitor_names.contains(&output_name))
{ {
layout_names.push(String::from(layout_name)) layout_names.push(String::from(layout_name))
} }
@@ -176,9 +186,10 @@ fn print_layout_names() {
} }
/// Apply the specified layout. /// Apply the specified layout.
/// layout_name may be the name of a layout rule, the name of a monitor, or the name of an output. /// layout_name may be the name of a layout in the config, the name of a monitor,
/// or the name of an output.
fn apply_layout(layout_name: &String) { fn apply_layout(layout_name: &String) {
let rules = get_rules(); let rules = get_config();
let outputs = get_outputs(); let outputs = get_outputs();
// Map of output name to config for all outputs to enable. // Map of output name to config for all outputs to enable.
@@ -193,7 +204,6 @@ fn apply_layout(layout_name: &String) {
let monitor = &rules.monitors[monitor_name]; let monitor = &rules.monitors[monitor_name];
// find the output for the monitor to get the output name. // find the output for the monitor to get the output name.
// (You can allegedly use "<make> <model> <serial>" instead of output name.)
let output_opt = outputs.iter().find(|output| { let output_opt = outputs.iter().find(|output| {
output.make == monitor.make output.make == monitor.make
&& output.model == monitor.model && output.model == monitor.model
@@ -211,8 +221,7 @@ fn apply_layout(layout_name: &String) {
if let Some(monitor) = rules.monitors.get(layout_name) { if let Some(monitor) = rules.monitors.get(layout_name) {
// It is a monitor name. Find the matching output... // It is a monitor name. Find the matching output...
if let Some(output) = outputs.iter().find(|output| { if let Some(output) = outputs.iter().find(|output| {
output.make == monitor.make output.make == monitor.make && output.model == monitor.model
&& output.model == monitor.model
&& output.serial == monitor.serial && output.serial == monitor.serial
}) { }) {
output_config_map.insert(&output.name, &output.output_config); output_config_map.insert(&output.name, &output.output_config);
@@ -235,12 +244,13 @@ fn apply_layout(layout_name: &String) {
/// Apply the specified outputs. Enable all outputs in [outputs], disable others. /// Apply the specified outputs. Enable all outputs in [outputs], disable others.
fn apply_outputs(all_outputs: &Vec<Output>, outputs: &HashMap<&String, &OutputConfig>) { fn apply_outputs(all_outputs: &Vec<Output>, outputs: &HashMap<&String, &OutputConfig>) {
let mut cmd = Command::new("swaymsg");
// enabled first, then disabled. // set enabled outputs first, then set disabled outputs.
// That way if some work before an error, you at least have one output enabled. // That way if some work before an error, you have at least one output enabled.
// for outputs to be enabled: map of output name to config
let mut enabled: HashMap<&String,&OutputConfig> = HashMap::new(); let mut enabled: HashMap<&String,&OutputConfig> = HashMap::new();
// for outputs to be disabled: output names
let mut disabled: Vec<&String> = Vec::new(); let mut disabled: Vec<&String> = Vec::new();
all_outputs.iter().for_each(|output| { all_outputs.iter().for_each(|output| {
@@ -251,6 +261,7 @@ fn apply_outputs(all_outputs: &Vec<Output>, outputs: &HashMap<&String, &OutputCo
} }
}); });
let mut cmd = Command::new("swaymsg");
enabled.iter().for_each(|(output_name,output_config)| { enabled.iter().for_each(|(output_name,output_config)| {
cmd.arg("output"); cmd.arg("output");
cmd.arg(&output_name); cmd.arg(&output_name);
@@ -274,6 +285,7 @@ fn apply_outputs(all_outputs: &Vec<Output>, outputs: &HashMap<&String, &OutputCo
cmd.arg(","); cmd.arg(",");
}); });
// print what we are about to do.
cmd.get_args() cmd.get_args()
.for_each(|arg| print!("{} ", arg.to_str().unwrap())); .for_each(|arg| print!("{} ", arg.to_str().unwrap()));