mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
config: Extract output
This commit is contained in:
+3
-350
@@ -14,8 +14,7 @@ use bitflags::bitflags;
|
||||
use knuffel::errors::DecodeError;
|
||||
use miette::{miette, Context, IntoDiagnostic};
|
||||
use niri_ipc::{
|
||||
ColumnDisplay, ConfiguredMode, LayoutSwitchTarget, PositionChange, SizeChange, Transform,
|
||||
WorkspaceReferenceArg,
|
||||
ColumnDisplay, LayoutSwitchTarget, PositionChange, SizeChange, WorkspaceReferenceArg,
|
||||
};
|
||||
use smithay::backend::renderer::Color32F;
|
||||
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
||||
@@ -28,6 +27,7 @@ pub const DEFAULT_BACKDROP_COLOR: Color = Color::from_array_unpremul([0.15, 0.15
|
||||
pub mod animations;
|
||||
pub mod input;
|
||||
pub mod layer_rule;
|
||||
pub mod output;
|
||||
pub mod utils;
|
||||
pub mod window_rule;
|
||||
|
||||
@@ -36,6 +36,7 @@ pub use crate::animations::{
|
||||
};
|
||||
pub use crate::input::{Input, ModKey, ScrollMethod, TrackLayout, WarpMouseToFocusMode, Xkb};
|
||||
pub use crate::layer_rule::LayerRule;
|
||||
pub use crate::output::{Output, OutputName, Outputs, Position, Vrr};
|
||||
pub use crate::window_rule::{FloatingPosition, RelativeTo, WindowRule};
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq)]
|
||||
@@ -107,86 +108,6 @@ pub enum CenterFocusedColumn {
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Percent(pub f64);
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Outputs(pub Vec<Output>);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Output {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(argument)]
|
||||
pub name: String,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scale: Option<FloatOrInt<0, 10>>,
|
||||
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
pub position: Option<Position>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mode: Option<ConfiguredMode>,
|
||||
#[knuffel(child)]
|
||||
pub variable_refresh_rate: Option<Vrr>,
|
||||
#[knuffel(child)]
|
||||
pub focus_at_startup: bool,
|
||||
#[knuffel(child)]
|
||||
pub background_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub backdrop_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn is_vrr_always_on(&self) -> bool {
|
||||
self.variable_refresh_rate == Some(Vrr { on_demand: false })
|
||||
}
|
||||
|
||||
pub fn is_vrr_on_demand(&self) -> bool {
|
||||
self.variable_refresh_rate == Some(Vrr { on_demand: true })
|
||||
}
|
||||
|
||||
pub fn is_vrr_always_off(&self) -> bool {
|
||||
self.variable_refresh_rate.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Output {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
focus_at_startup: false,
|
||||
name: String::new(),
|
||||
scale: None,
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
mode: None,
|
||||
variable_refresh_rate: None,
|
||||
background_color: None,
|
||||
backdrop_color: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputName {
|
||||
pub connector: String,
|
||||
pub make: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub serial: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Position {
|
||||
#[knuffel(property)]
|
||||
pub x: i32,
|
||||
#[knuffel(property)]
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Default)]
|
||||
pub struct Vrr {
|
||||
#[knuffel(property, default = false)]
|
||||
pub on_demand: bool,
|
||||
}
|
||||
|
||||
// MIN and MAX generics are only used during parsing to check the value.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
|
||||
@@ -2263,129 +2184,6 @@ fn expect_only_children<S>(
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Output> for Outputs {
|
||||
fn from_iter<T: IntoIterator<Item = Output>>(iter: T) -> Self {
|
||||
Self(Vec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
impl Outputs {
|
||||
pub fn find(&self, name: &OutputName) -> Option<&Output> {
|
||||
self.0.iter().find(|o| name.matches(&o.name))
|
||||
}
|
||||
|
||||
pub fn find_mut(&mut self, name: &OutputName) -> Option<&mut Output> {
|
||||
self.0.iter_mut().find(|o| name.matches(&o.name))
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputName {
|
||||
pub fn from_ipc_output(output: &niri_ipc::Output) -> Self {
|
||||
Self {
|
||||
connector: output.name.clone(),
|
||||
make: (output.make != "Unknown").then(|| output.make.clone()),
|
||||
model: (output.model != "Unknown").then(|| output.model.clone()),
|
||||
serial: output.serial.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an output description matching what Smithay's `Output::new()` does.
|
||||
pub fn format_description(&self) -> String {
|
||||
format!(
|
||||
"{} - {} - {}",
|
||||
self.make.as_deref().unwrap_or("Unknown"),
|
||||
self.model.as_deref().unwrap_or("Unknown"),
|
||||
self.connector,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an output name that will match by make/model/serial or, if they are missing, by
|
||||
/// connector.
|
||||
pub fn format_make_model_serial_or_connector(&self) -> String {
|
||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||
self.connector.to_string()
|
||||
} else {
|
||||
self.format_make_model_serial()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_make_model_serial(&self) -> String {
|
||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||
format!("{make} {model} {serial}")
|
||||
}
|
||||
|
||||
pub fn matches(&self, target: &str) -> bool {
|
||||
// Match by connector.
|
||||
if target.eq_ignore_ascii_case(&self.connector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no other fields are available, don't try to match by them.
|
||||
//
|
||||
// This is used by niri msg output.
|
||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match by "make model serial" with Unknown if something is missing.
|
||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||
|
||||
let Some(target_make) = target.get(..make.len()) else {
|
||||
return false;
|
||||
};
|
||||
let rest = &target[make.len()..];
|
||||
if !target_make.eq_ignore_ascii_case(make) {
|
||||
return false;
|
||||
}
|
||||
if !rest.starts_with(' ') {
|
||||
return false;
|
||||
}
|
||||
let rest = &rest[1..];
|
||||
|
||||
let Some(target_model) = rest.get(..model.len()) else {
|
||||
return false;
|
||||
};
|
||||
let rest = &rest[model.len()..];
|
||||
if !target_model.eq_ignore_ascii_case(model) {
|
||||
return false;
|
||||
}
|
||||
if !rest.starts_with(' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let rest = &rest[1..];
|
||||
if !rest.eq_ignore_ascii_case(serial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Similar in spirit to Ord, but I don't want to derive Eq to avoid mistakes (you should use
|
||||
// `Self::match`, not Eq).
|
||||
pub fn compare(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let self_missing_mms = self.make.is_none() && self.model.is_none() && self.serial.is_none();
|
||||
let other_missing_mms =
|
||||
other.make.is_none() && other.model.is_none() && other.serial.is_none();
|
||||
|
||||
match (self_missing_mms, other_missing_mms) {
|
||||
(true, true) => self.connector.cmp(&other.connector),
|
||||
(true, false) => std::cmp::Ordering::Greater,
|
||||
(false, true) => std::cmp::Ordering::Less,
|
||||
(false, false) => self
|
||||
.make
|
||||
.cmp(&other.make)
|
||||
.then_with(|| self.model.cmp(&other.model))
|
||||
.then_with(|| self.serial.cmp(&other.serial))
|
||||
.then_with(|| self.connector.cmp(&other.connector)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for DefaultPresetSize
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
@@ -4601,32 +4399,6 @@ mod tests {
|
||||
let _ = Config::default();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mode() {
|
||||
assert_eq!(
|
||||
"2560x1600@165.004".parse::<ConfiguredMode>().unwrap(),
|
||||
ConfiguredMode {
|
||||
width: 2560,
|
||||
height: 1600,
|
||||
refresh: Some(165.004),
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"1920x1080".parse::<ConfiguredMode>().unwrap(),
|
||||
ConfiguredMode {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
refresh: None,
|
||||
},
|
||||
);
|
||||
|
||||
assert!("1920".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x1080@".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x1080@60Hz".parse::<ConfiguredMode>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_size_change() {
|
||||
assert_eq!(
|
||||
@@ -4795,125 +4567,6 @@ mod tests {
|
||||
assert_eq!(config.input.keyboard.repeat_rate, 25);
|
||||
}
|
||||
|
||||
fn make_output_name(
|
||||
connector: &str,
|
||||
make: Option<&str>,
|
||||
model: Option<&str>,
|
||||
serial: Option<&str>,
|
||||
) -> OutputName {
|
||||
OutputName {
|
||||
connector: connector.to_string(),
|
||||
make: make.map(|x| x.to_string()),
|
||||
model: model.map(|x| x.to_string()),
|
||||
serial: serial.map(|x| x.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_name_match() {
|
||||
fn check(
|
||||
target: &str,
|
||||
connector: &str,
|
||||
make: Option<&str>,
|
||||
model: Option<&str>,
|
||||
serial: Option<&str>,
|
||||
) -> bool {
|
||||
let name = make_output_name(connector, make, model, serial);
|
||||
name.matches(target)
|
||||
}
|
||||
|
||||
assert!(check("dp-2", "DP-2", None, None, None));
|
||||
assert!(!check("dp-1", "DP-2", None, None, None));
|
||||
assert!(check("dp-2", "DP-2", Some("a"), Some("b"), Some("c")));
|
||||
assert!(check(
|
||||
"some company some monitor 1234",
|
||||
"DP-2",
|
||||
Some("Some Company"),
|
||||
Some("Some Monitor"),
|
||||
Some("1234")
|
||||
));
|
||||
assert!(!check(
|
||||
"some other company some monitor 1234",
|
||||
"DP-2",
|
||||
Some("Some Company"),
|
||||
Some("Some Monitor"),
|
||||
Some("1234")
|
||||
));
|
||||
assert!(!check(
|
||||
"make model serial ",
|
||||
"DP-2",
|
||||
Some("make"),
|
||||
Some("model"),
|
||||
Some("serial")
|
||||
));
|
||||
assert!(check(
|
||||
"make serial",
|
||||
"DP-2",
|
||||
Some("make"),
|
||||
Some(""),
|
||||
Some("serial")
|
||||
));
|
||||
assert!(check(
|
||||
"make model unknown",
|
||||
"DP-2",
|
||||
Some("Make"),
|
||||
Some("Model"),
|
||||
None
|
||||
));
|
||||
assert!(check(
|
||||
"unknown unknown serial",
|
||||
"DP-2",
|
||||
None,
|
||||
None,
|
||||
Some("Serial")
|
||||
));
|
||||
assert!(!check("unknown unknown unknown", "DP-2", None, None, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_name_sorting() {
|
||||
let mut names = vec![
|
||||
make_output_name("DP-2", None, None, None),
|
||||
make_output_name("DP-1", None, None, None),
|
||||
make_output_name("DP-3", Some("B"), Some("A"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("B"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("A"), Some("B")),
|
||||
make_output_name("DP-3", None, Some("A"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), None, Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("A"), None),
|
||||
make_output_name("DP-5", Some("A"), Some("A"), Some("A")),
|
||||
make_output_name("DP-4", Some("A"), Some("A"), Some("A")),
|
||||
];
|
||||
names.sort_by(|a, b| a.compare(b));
|
||||
let names = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
format!(
|
||||
"{} | {}",
|
||||
name.format_make_model_serial_or_connector(),
|
||||
name.connector,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
names,
|
||||
@r#"
|
||||
[
|
||||
"Unknown A A | DP-3",
|
||||
"A Unknown A | DP-3",
|
||||
"A A Unknown | DP-3",
|
||||
"A A A | DP-4",
|
||||
"A A A | DP-5",
|
||||
"A A B | DP-3",
|
||||
"A B A | DP-3",
|
||||
"B A A | DP-3",
|
||||
"DP-1 | DP-1",
|
||||
"DP-2 | DP-2",
|
||||
]
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_border_rule_on_off_merging() {
|
||||
fn is_on(config: &str, rules: &[&str]) -> String {
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
use niri_ipc::{ConfiguredMode, Transform};
|
||||
|
||||
use crate::{Color, FloatOrInt};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Outputs(pub Vec<Output>);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Output {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(argument)]
|
||||
pub name: String,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scale: Option<FloatOrInt<0, 10>>,
|
||||
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
pub position: Option<Position>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub mode: Option<ConfiguredMode>,
|
||||
#[knuffel(child)]
|
||||
pub variable_refresh_rate: Option<Vrr>,
|
||||
#[knuffel(child)]
|
||||
pub focus_at_startup: bool,
|
||||
#[knuffel(child)]
|
||||
pub background_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
pub backdrop_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn is_vrr_always_on(&self) -> bool {
|
||||
self.variable_refresh_rate == Some(Vrr { on_demand: false })
|
||||
}
|
||||
|
||||
pub fn is_vrr_on_demand(&self) -> bool {
|
||||
self.variable_refresh_rate == Some(Vrr { on_demand: true })
|
||||
}
|
||||
|
||||
pub fn is_vrr_always_off(&self) -> bool {
|
||||
self.variable_refresh_rate.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Output {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
focus_at_startup: false,
|
||||
name: String::new(),
|
||||
scale: None,
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
mode: None,
|
||||
variable_refresh_rate: None,
|
||||
background_color: None,
|
||||
backdrop_color: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputName {
|
||||
pub connector: String,
|
||||
pub make: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub serial: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Position {
|
||||
#[knuffel(property)]
|
||||
pub x: i32,
|
||||
#[knuffel(property)]
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Default)]
|
||||
pub struct Vrr {
|
||||
#[knuffel(property, default = false)]
|
||||
pub on_demand: bool,
|
||||
}
|
||||
|
||||
impl FromIterator<Output> for Outputs {
|
||||
fn from_iter<T: IntoIterator<Item = Output>>(iter: T) -> Self {
|
||||
Self(Vec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
impl Outputs {
|
||||
pub fn find(&self, name: &OutputName) -> Option<&Output> {
|
||||
self.0.iter().find(|o| name.matches(&o.name))
|
||||
}
|
||||
|
||||
pub fn find_mut(&mut self, name: &OutputName) -> Option<&mut Output> {
|
||||
self.0.iter_mut().find(|o| name.matches(&o.name))
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputName {
|
||||
pub fn from_ipc_output(output: &niri_ipc::Output) -> Self {
|
||||
Self {
|
||||
connector: output.name.clone(),
|
||||
make: (output.make != "Unknown").then(|| output.make.clone()),
|
||||
model: (output.model != "Unknown").then(|| output.model.clone()),
|
||||
serial: output.serial.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an output description matching what Smithay's `Output::new()` does.
|
||||
pub fn format_description(&self) -> String {
|
||||
format!(
|
||||
"{} - {} - {}",
|
||||
self.make.as_deref().unwrap_or("Unknown"),
|
||||
self.model.as_deref().unwrap_or("Unknown"),
|
||||
self.connector,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an output name that will match by make/model/serial or, if they are missing, by
|
||||
/// connector.
|
||||
pub fn format_make_model_serial_or_connector(&self) -> String {
|
||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||
self.connector.to_string()
|
||||
} else {
|
||||
self.format_make_model_serial()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_make_model_serial(&self) -> String {
|
||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||
format!("{make} {model} {serial}")
|
||||
}
|
||||
|
||||
pub fn matches(&self, target: &str) -> bool {
|
||||
// Match by connector.
|
||||
if target.eq_ignore_ascii_case(&self.connector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no other fields are available, don't try to match by them.
|
||||
//
|
||||
// This is used by niri msg output.
|
||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match by "make model serial" with Unknown if something is missing.
|
||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||
|
||||
let Some(target_make) = target.get(..make.len()) else {
|
||||
return false;
|
||||
};
|
||||
let rest = &target[make.len()..];
|
||||
if !target_make.eq_ignore_ascii_case(make) {
|
||||
return false;
|
||||
}
|
||||
if !rest.starts_with(' ') {
|
||||
return false;
|
||||
}
|
||||
let rest = &rest[1..];
|
||||
|
||||
let Some(target_model) = rest.get(..model.len()) else {
|
||||
return false;
|
||||
};
|
||||
let rest = &rest[model.len()..];
|
||||
if !target_model.eq_ignore_ascii_case(model) {
|
||||
return false;
|
||||
}
|
||||
if !rest.starts_with(' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let rest = &rest[1..];
|
||||
if !rest.eq_ignore_ascii_case(serial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Similar in spirit to Ord, but I don't want to derive Eq to avoid mistakes (you should use
|
||||
// `Self::match`, not Eq).
|
||||
pub fn compare(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let self_missing_mms = self.make.is_none() && self.model.is_none() && self.serial.is_none();
|
||||
let other_missing_mms =
|
||||
other.make.is_none() && other.model.is_none() && other.serial.is_none();
|
||||
|
||||
match (self_missing_mms, other_missing_mms) {
|
||||
(true, true) => self.connector.cmp(&other.connector),
|
||||
(true, false) => std::cmp::Ordering::Greater,
|
||||
(false, true) => std::cmp::Ordering::Less,
|
||||
(false, false) => self
|
||||
.make
|
||||
.cmp(&other.make)
|
||||
.then_with(|| self.model.cmp(&other.model))
|
||||
.then_with(|| self.serial.cmp(&other.serial))
|
||||
.then_with(|| self.connector.cmp(&other.connector)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_mode() {
|
||||
assert_eq!(
|
||||
"2560x1600@165.004".parse::<ConfiguredMode>().unwrap(),
|
||||
ConfiguredMode {
|
||||
width: 2560,
|
||||
height: 1600,
|
||||
refresh: Some(165.004),
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"1920x1080".parse::<ConfiguredMode>().unwrap(),
|
||||
ConfiguredMode {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
refresh: None,
|
||||
},
|
||||
);
|
||||
|
||||
assert!("1920".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x1080@".parse::<ConfiguredMode>().is_err());
|
||||
assert!("1920x1080@60Hz".parse::<ConfiguredMode>().is_err());
|
||||
}
|
||||
|
||||
fn make_output_name(
|
||||
connector: &str,
|
||||
make: Option<&str>,
|
||||
model: Option<&str>,
|
||||
serial: Option<&str>,
|
||||
) -> OutputName {
|
||||
OutputName {
|
||||
connector: connector.to_string(),
|
||||
make: make.map(|x| x.to_string()),
|
||||
model: model.map(|x| x.to_string()),
|
||||
serial: serial.map(|x| x.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_name_match() {
|
||||
fn check(
|
||||
target: &str,
|
||||
connector: &str,
|
||||
make: Option<&str>,
|
||||
model: Option<&str>,
|
||||
serial: Option<&str>,
|
||||
) -> bool {
|
||||
let name = make_output_name(connector, make, model, serial);
|
||||
name.matches(target)
|
||||
}
|
||||
|
||||
assert!(check("dp-2", "DP-2", None, None, None));
|
||||
assert!(!check("dp-1", "DP-2", None, None, None));
|
||||
assert!(check("dp-2", "DP-2", Some("a"), Some("b"), Some("c")));
|
||||
assert!(check(
|
||||
"some company some monitor 1234",
|
||||
"DP-2",
|
||||
Some("Some Company"),
|
||||
Some("Some Monitor"),
|
||||
Some("1234")
|
||||
));
|
||||
assert!(!check(
|
||||
"some other company some monitor 1234",
|
||||
"DP-2",
|
||||
Some("Some Company"),
|
||||
Some("Some Monitor"),
|
||||
Some("1234")
|
||||
));
|
||||
assert!(!check(
|
||||
"make model serial ",
|
||||
"DP-2",
|
||||
Some("make"),
|
||||
Some("model"),
|
||||
Some("serial")
|
||||
));
|
||||
assert!(check(
|
||||
"make serial",
|
||||
"DP-2",
|
||||
Some("make"),
|
||||
Some(""),
|
||||
Some("serial")
|
||||
));
|
||||
assert!(check(
|
||||
"make model unknown",
|
||||
"DP-2",
|
||||
Some("Make"),
|
||||
Some("Model"),
|
||||
None
|
||||
));
|
||||
assert!(check(
|
||||
"unknown unknown serial",
|
||||
"DP-2",
|
||||
None,
|
||||
None,
|
||||
Some("Serial")
|
||||
));
|
||||
assert!(!check("unknown unknown unknown", "DP-2", None, None, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_name_sorting() {
|
||||
let mut names = vec![
|
||||
make_output_name("DP-2", None, None, None),
|
||||
make_output_name("DP-1", None, None, None),
|
||||
make_output_name("DP-3", Some("B"), Some("A"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("B"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("A"), Some("B")),
|
||||
make_output_name("DP-3", None, Some("A"), Some("A")),
|
||||
make_output_name("DP-3", Some("A"), None, Some("A")),
|
||||
make_output_name("DP-3", Some("A"), Some("A"), None),
|
||||
make_output_name("DP-5", Some("A"), Some("A"), Some("A")),
|
||||
make_output_name("DP-4", Some("A"), Some("A"), Some("A")),
|
||||
];
|
||||
names.sort_by(|a, b| a.compare(b));
|
||||
let names = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
format!(
|
||||
"{} | {}",
|
||||
name.format_make_model_serial_or_connector(),
|
||||
name.connector,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
names,
|
||||
@r#"
|
||||
[
|
||||
"Unknown A A | DP-3",
|
||||
"A Unknown A | DP-3",
|
||||
"A A Unknown | DP-3",
|
||||
"A A A | DP-4",
|
||||
"A A A | DP-5",
|
||||
"A A B | DP-3",
|
||||
"A B A | DP-3",
|
||||
"B A A | DP-3",
|
||||
"DP-1 | DP-1",
|
||||
"DP-2 | DP-2",
|
||||
]
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user