use KDL instead of TOML
This commit is contained in:
132
Cargo.lock
generated
132
Cargo.lock
generated
@@ -2,40 +2,74 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.15.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kdl"
|
||||||
|
version = "4.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e03e2e96c5926fe761088d66c8c2aee3a4352a2573f4eaca50043ad130af9117"
|
||||||
|
dependencies = [
|
||||||
|
"miette",
|
||||||
|
"nom",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
|
||||||
|
dependencies = [
|
||||||
|
"miette-derive",
|
||||||
|
"once_cell",
|
||||||
|
"thiserror",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette-derive"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.92"
|
version = "1.0.92"
|
||||||
@@ -92,22 +126,13 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swayout"
|
name = "swayout"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"kdl",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"toml",
|
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -123,37 +148,23 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "thiserror"
|
||||||
version = "0.8.19"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"thiserror-impl",
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "thiserror-impl"
|
||||||
version = "0.6.8"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"proc-macro2",
|
||||||
]
|
"quote",
|
||||||
|
"syn",
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"winnow",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -163,13 +174,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "unicode-width"
|
||||||
version = "0.6.20"
|
version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xdg"
|
name = "xdg"
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ authors = ["Stephen Byrne"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# for config yaml
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
toml = "0.8.19"
|
|
||||||
|
|
||||||
# for json output from swaymsg
|
# for json output from swaymsg
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
|
|
||||||
|
# for config file
|
||||||
|
kdl = "4.7.1"
|
||||||
|
|
||||||
xdg = "2.5.2"
|
xdg = "2.5.2"
|
||||||
58
README.md
58
README.md
@@ -5,55 +5,35 @@
|
|||||||
[kanshi](https://sr.ht/~emersion/kanshi/) and [way-displays](https://github.com/alex-courtis/way-displays) certainly
|
[kanshi](https://sr.ht/~emersion/kanshi/) and [way-displays](https://github.com/alex-courtis/way-displays) certainly
|
||||||
do a better job of this. I wrote this because I wanted a project to learn Rust.
|
do a better job of this. I wrote this because I wanted a project to learn Rust.
|
||||||
|
|
||||||
Monitors and layouts are configured in a toml file
|
Monitors and layouts are configured in a [KDL](https://kdl.dev) file
|
||||||
(`~/.config/swayout/config.toml`).
|
(`~/.config/swayout/config.kdl`).
|
||||||
|
|
||||||
Example `config.toml`:
|
Example `config.kdl`:
|
||||||
|
|
||||||
```toml
|
```kdl
|
||||||
# This is my laptop's built-in screen.
|
// This is my laptop's built-in screen.
|
||||||
[monitors.laptop]
|
monitor "laptop" make="Unknown" model="0x403D" serial="0x00000000"
|
||||||
make = "Unknown"
|
|
||||||
model = "0x403D"
|
|
||||||
serial = "0x00000000"
|
|
||||||
|
|
||||||
# I usually have it hooked up to this monitor, which is the top one on my desk.
|
// I usually have it hooked up to this monitor, which is the top one on my desk.
|
||||||
[monitors.top]
|
monitor "top" make="Dell Inc." model="DELL U2415" serial="CFV9N99T0Y0U"
|
||||||
make = "Dell Inc."
|
|
||||||
model = "DELL U2415"
|
|
||||||
serial = "CFV9N99T0Y0U"
|
|
||||||
|
|
||||||
# Sometimes I hook it up to these monitors, which are left and right.
|
// Sometimes I hook it up to these monitors, which are left and right.
|
||||||
# The left is rotated 270 degrees.
|
// The left is in portrait.
|
||||||
[monitors.left]
|
monitor "left" make="Goldstar Company Ltd" model="LG HDR 4K" serial="0x0000B9C0"
|
||||||
make = "Goldstar Company Ltd"
|
monitor "right" make="Goldstar Company Ltd" model="LG HDR 4K" serial="0x0000B9BF"
|
||||||
model = "LG HDR 4K"
|
|
||||||
serial = "0x0000B9C0"
|
|
||||||
[monitors.right]
|
|
||||||
make = "Goldstar Company Ltd"
|
|
||||||
model = "LG HDR 4K"
|
|
||||||
serial = "0x0000B9BF"
|
|
||||||
|
|
||||||
# When I have "left" and "right" hooked up, I want this layout.
|
// When I have "left" and "right" hooked up, I want this layout.
|
||||||
[layouts.two4k.left]
|
layout "two4k" {
|
||||||
mode = "3840x2160"
|
output "left" mode="3840x2160" scale="1.5" x=0 y=0 transform="270"
|
||||||
scale = "1.5"
|
output "right" mode="3840x2160" scale="1.5" x=1440 y=1120 transform="normal"
|
||||||
x = 0
|
}
|
||||||
y = 0
|
|
||||||
transform = "270"
|
|
||||||
[layouts.two4k.right]
|
|
||||||
mode = "3840x2160"
|
|
||||||
scale = "1.5"
|
|
||||||
x = 1440
|
|
||||||
y = 1120
|
|
||||||
transform = "normal"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When you run `swayout`, it prints all available layout names to standard output, one per line.
|
When you run `swayout`, it prints all available layout names to standard output, one per line.
|
||||||
Printed layouts include:
|
Printed layouts include:
|
||||||
|
|
||||||
* All layouts defined in `config.toml` for which the monitors are currently available. E.g.: `two4k`.
|
* All layouts defined in `config.kdl` for which the monitors are currently available. E.g.: `two4k`.
|
||||||
* All monitors defined in `config.toml` that are currently available. E.g.: `laptop`, `left`, `right`.
|
* All monitors defined in `config.kdl` that are currently available. E.g.: `laptop`, `left`, `right`.
|
||||||
* All available outputs that are not matched by a configured monitor. E.g.: `HDMI-2`.
|
* All available outputs that are not matched by a configured monitor. E.g.: `HDMI-2`.
|
||||||
|
|
||||||
If you pass a layout name (or monitor name, or output name) as a command line argument to `swayout`,
|
If you pass a layout name (or monitor name, or output name) as a command line argument to `swayout`,
|
||||||
|
|||||||
67
src/main.rs
67
src/main.rs
@@ -1,4 +1,4 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use kdl::KdlDocument;
|
||||||
use serde_json::{from_str, Value};
|
use serde_json::{from_str, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
@@ -6,7 +6,6 @@ use std::process::Command;
|
|||||||
use std::{fs, str};
|
use std::{fs, str};
|
||||||
|
|
||||||
/// Configuration for swayout.
|
/// Configuration for swayout.
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct Config {
|
struct Config {
|
||||||
/// Monitor name is independent of output name. It can be anything.
|
/// Monitor name is independent of output name. It can be anything.
|
||||||
monitors: HashMap<String, Monitor>,
|
monitors: HashMap<String, Monitor>,
|
||||||
@@ -16,15 +15,15 @@ struct Config {
|
|||||||
/// Available outputs that do not match a monitor in the map are disabled.
|
/// 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.
|
/// Defines a monitor by make, model, and serial.
|
||||||
#[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.
|
/// Configuration for an enabled output.
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct OutputConfig {
|
struct OutputConfig {
|
||||||
/// mode. See man 5 sway-output
|
/// mode. See man 5 sway-output
|
||||||
mode: String,
|
mode: String,
|
||||||
@@ -112,13 +111,13 @@ fn get_mode(output: &Value) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a `Config` from ~/.config/swayout/config.toml.
|
/// Get a `Config` from ~/.config/swayout/config.kdl.
|
||||||
/// (It really uses XDG config dirs.)
|
/// (It really uses XDG config dirs.)
|
||||||
fn get_config() -> Config {
|
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.kdl") {
|
||||||
let toml = fs::read_to_string(path).expect("error reading file");
|
let kdl = fs::read_to_string(path).expect("error reading file");
|
||||||
toml::from_str(&toml).expect("toml parse error")
|
parse_config(kdl)
|
||||||
} else {
|
} else {
|
||||||
// no config files, use an empty rules
|
// no config files, use an empty rules
|
||||||
Config {
|
Config {
|
||||||
@@ -127,6 +126,46 @@ fn get_config() -> Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn parse_config(kdl:String) -> Config {
|
||||||
|
let doc:KdlDocument = kdl.parse().expect("failed to parse config KDL");
|
||||||
|
|
||||||
|
let monitors:HashMap<String,Monitor> = doc.nodes().iter()
|
||||||
|
.filter(|node| node.name().value() == "monitor")
|
||||||
|
.map(|monitor_node| {
|
||||||
|
let name = String::from(monitor_node[0].as_string().unwrap());
|
||||||
|
let monitor = Monitor {
|
||||||
|
make : String::from(monitor_node["make"].as_string().unwrap()),
|
||||||
|
model : String::from(monitor_node["model"].as_string().unwrap()),
|
||||||
|
serial : String::from(monitor_node["serial"].as_string().unwrap()),
|
||||||
|
};
|
||||||
|
(name, monitor)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let layouts:HashMap<String,HashMap<String,OutputConfig>> = doc.nodes().iter()
|
||||||
|
.filter(|node| node.name().value() == "layout")
|
||||||
|
.map(|layout_node| {
|
||||||
|
let layout_name = String::from(layout_node[0].as_string().unwrap());
|
||||||
|
let monitor_name_to_output_config:HashMap<String,OutputConfig> = layout_node
|
||||||
|
.children().unwrap().nodes().iter()
|
||||||
|
.map(|output_node| {
|
||||||
|
let output = String::from(output_node[0].as_string().unwrap());
|
||||||
|
let output_config = OutputConfig {
|
||||||
|
mode : String::from(output_node["mode"].as_string().unwrap()),
|
||||||
|
scale : String::from(output_node["scale"].as_string().unwrap()),
|
||||||
|
transform : String::from(output_node["transform"].as_string().unwrap()),
|
||||||
|
x : output_node["x"].as_i64().unwrap() as u16,
|
||||||
|
y : output_node["y"].as_i64().unwrap() as u16,
|
||||||
|
};
|
||||||
|
(output, output_config)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(layout_name,monitor_name_to_output_config)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Config { monitors, layouts }
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
@@ -135,12 +174,10 @@ fn get_config() -> Config {
|
|||||||
/// 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_config();
|
let config = get_config();
|
||||||
|
|
||||||
let mut layout_names: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
// Get the names of monitors that are available (that match an available output)
|
// Get the names of monitors that are available (that match an available output)
|
||||||
let available_monitor_names:Vec<&String> = rules.monitors.iter()
|
let available_monitor_names:Vec<&String> = config.monitors.iter()
|
||||||
.filter(|(_monitor_name,monitor)|
|
.filter(|(_monitor_name,monitor)|
|
||||||
available_outputs.iter().any(|output|
|
available_outputs.iter().any(|output|
|
||||||
monitor.make == output.make
|
monitor.make == output.make
|
||||||
@@ -151,8 +188,10 @@ fn get_layouts() -> Vec<String> {
|
|||||||
.map(|(monitor_name,_monitor)| monitor_name)
|
.map(|(monitor_name,_monitor)| monitor_name)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let mut layout_names: Vec<String> = Vec::new();
|
||||||
|
|
||||||
// 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)| {
|
config.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))
|
||||||
@@ -164,7 +203,7 @@ fn get_layouts() -> Vec<String> {
|
|||||||
// add each individual output (by monitor name if the monitor is defined, else by output name)
|
// add each individual output (by monitor name if the monitor is defined, else by output name)
|
||||||
available_outputs.iter().for_each(|output| {
|
available_outputs.iter().for_each(|output| {
|
||||||
// if there is a monitor for the output, use the monitor name, else use the output name
|
// if there is a monitor for the output, use the monitor name, else use the output name
|
||||||
let monitor_opt = rules.monitors.iter()
|
let monitor_opt = config.monitors.iter()
|
||||||
.find(|(_monitor_name, monitor)| {
|
.find(|(_monitor_name, monitor)| {
|
||||||
monitor.make == output.make
|
monitor.make == output.make
|
||||||
&& monitor.model == output.model
|
&& monitor.model == output.model
|
||||||
|
|||||||
Reference in New Issue
Block a user