gnome-randr 0.1.0

A reimplementation of xrandr for Gnome on Wayland
Documentation
mod actions;

use gnome_randr::{display_config::ApplyConfig, DisplayConfig};
use structopt::StructOpt;

use self::actions::{Action, ModeAction, PrimaryAction, RotationAction, ScaleAction};

#[derive(Clone, Copy)]
pub enum Rotation {
    Normal,
    Left,
    Right,
    Inverted,
}

impl std::str::FromStr for Rotation {
    type Err = std::fmt::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "normal" => Ok(Rotation::Normal),
            "left" => Ok(Rotation::Left),
            "right" => Ok(Rotation::Right),
            "inverted" => Ok(Rotation::Inverted),
            _ => Err(std::fmt::Error),
        }
    }
}

impl std::fmt::Display for Rotation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Rotation::Normal => "normal",
                Rotation::Left => "left",
                Rotation::Right => "right",
                Rotation::Inverted => "inverted",
            }
        )
    }
}

#[derive(StructOpt)]
pub struct ActionOptions {
    #[structopt(
        short,
        long = "rotate",
        help = "One of 'normal', 'left', 'right' or 'inverted'",
        long_help = "One of 'normal', 'left', 'right' or 'inverted'. This causes the output contents to be rotated in the specified direction. 'right' specifies a clockwise rotation of the picture and 'left' specifies a counter-clockwise rotation."
    )]
    pub rotation: Option<Rotation>,

    #[structopt(
        short,
        long,
        help = "A valid mode for the given display.",
        long_help = "A valid mode for the given display. To find valid modes use the \"query\" subcommand"
    )]
    pub mode: Option<String>,

    #[structopt(long, help = "Set the given monitor as the primary logical monitor")]
    pub primary: bool,

    #[structopt(long, help = "Set the scale")]
    pub scale: Option<f64>,
}

#[derive(StructOpt)]
pub struct CommandOptions {
    #[structopt(
        help = "the connector used for the physical monitor.",
        long_help = "the connector used for the physical monitor you want to modify, e.g. \"HDMI-1\". You can find these with \"query\" (no arguments) if you're unsure."
    )]
    pub connector: String,

    #[structopt(flatten)]
    pub actions: ActionOptions,

    #[structopt(
        short,
        long,
        help = "Attempt to replicate this configuration the next time this HW layout appears"
    )]
    persistent: bool,

    #[structopt(long, help = "List changes without actually applying them")]
    dry_run: bool,
}

#[derive(Debug)]
pub enum Error {
    NotFound,
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match &self {
                Error::NotFound => "fatal: unable to find output.",
            }
        )
    }
}

impl std::error::Error for Error {}

pub fn handle(
    opts: &CommandOptions,
    config: &DisplayConfig,
    proxy: &dbus::blocking::Proxy<&dbus::blocking::Connection>,
) -> Result<(), Box<dyn std::error::Error>> {
    let (logical_monitor, physical_monitor) =
        config.search(&opts.connector).ok_or(Error::NotFound)?;

    let mut actions = Vec::<Box<dyn Action>>::new();
    let primary_is_changing = opts.actions.primary;

    if let Some(rotation) = &opts.actions.rotation {
        actions.push(Box::new(RotationAction {
            rotation: *rotation,
        }));
    }

    if let Some(mode_id) = &opts.actions.mode {
        actions.push(Box::new(ModeAction { mode: mode_id }))
    }

    if opts.actions.primary {
        actions.push(Box::new(PrimaryAction {}));
    }

    if let Some(scale) = &opts.actions.scale {
        actions.push(Box::new(ScaleAction { scale: *scale }))
    }

    if actions.is_empty() {
        println!("no changes made.");
        return Ok(());
    }

    let mut apply_config = ApplyConfig::from(logical_monitor, physical_monitor);

    if opts.persistent {
        println!("attempting to persist config to disk")
    }

    for action in actions.iter() {
        println!("{}", &action);
        action.apply(&mut apply_config, physical_monitor);
    }

    if opts.dry_run {
        println!("dry run: no changes made.");
        return Ok(());
    }

    let all_configs = config
        .monitors
        .iter()
        .filter_map(|monitor| {
            if monitor.connector == opts.connector {
                return Some(apply_config.clone());
            }

            let (logical_monitor, _) = match config.search(&monitor.connector) {
                Some(monitors) => monitors,
                None => return None,
            };

            let mut apply_config = ApplyConfig::from(logical_monitor, monitor);

            if primary_is_changing {
                apply_config.primary = false;
            }

            Some(apply_config)
        })
        .collect();

    config.apply_monitors_config(proxy, all_configs, opts.persistent)?;

    Ok(())
}