diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bff02a44..07d78d56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -236,7 +236,7 @@ jobs: target: "aarch64-linux-android", cross: true, rust: stable, - cross-version: "git:d6511b7b166c18640f81b8f6a74d9eef380f7ded", + cross-version: "git:cabfc3b02d1edec03869fabdabf6a7f8b0519160", no-default-features: true, } diff --git a/Cargo.lock b/Cargo.lock index 30e79e1a..f7f171f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,6 +176,7 @@ dependencies = [ "log", "mach2", "nvml-wrapper", + "portable-pty", "predicates", "ratatui", "regex", @@ -352,7 +353,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", ] [[package]] @@ -476,6 +477,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "either" version = "1.9.0" @@ -606,6 +613,15 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +[[package]] +name = "ioctl-rs" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" +dependencies = [ + "libc", +] + [[package]] name = "itertools" version = "0.11.0" @@ -639,6 +655,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "lazycell" version = "1.3.0" @@ -718,6 +740,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -748,6 +779,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", + "pin-utils", +] + [[package]] name = "nix" version = "0.26.4" @@ -876,6 +921,33 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-pty" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.25.1", + "serial", + "shared_library", + "shell-words", + "winapi", + "winreg", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1111,6 +1183,64 @@ dependencies = [ "serde", ] +[[package]] +name = "serial" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" +dependencies = [ + "serial-core", + "serial-unix", + "serial-windows", +] + +[[package]] +name = "serial-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" +dependencies = [ + "libc", +] + +[[package]] +name = "serial-unix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" +dependencies = [ + "ioctl-rs", + "libc", + "serial-core", + "termios", +] + +[[package]] +name = "serial-windows" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" +dependencies = [ + "libc", + "serial-core", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signal-hook" version = "0.3.17" @@ -1275,6 +1405,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "termios" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" +dependencies = [ + "libc", +] + [[package]] name = "termtree" version = "0.4.1" @@ -1616,6 +1755,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "wrapcenum-derive" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 39a0cfce..45805ddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,6 +135,9 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } predicates = "3.0.3" +[target.'cfg(all(target_arch = "x86_64", target_os = "linux"))'.dev-dependencies] +portable-pty = "0.8.1" + [build-dependencies] clap = { version = "4.4.14", features = ["default", "cargo", "wrap_help"] } clap_complete = "4.4.6" diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index ca353f52..e7904c9a 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -66,20 +66,20 @@ impl BottomLayout { col_row_mapping.insert( ( widget_width * 100 / col_row.total_widget_ratio, - (widget_width + widget.width_ratio) * 100 + (widget_width + widget.constraint.ratio()) * 100 / col_row.total_widget_ratio, ), widget.widget_id, ); } } - widget_width += widget.width_ratio; + widget_width += widget.constraint.ratio(); } if is_valid_col_row { col_mapping.insert( ( col_row_height * 100 / col.total_col_row_ratio, - (col_row_height + col_row.col_row_height_ratio) * 100 + (col_row_height + col_row.constraint.ratio()) * 100 / col.total_col_row_ratio, ), (col.total_col_row_ratio, col_row_mapping), @@ -87,31 +87,31 @@ impl BottomLayout { is_valid_col = true; } - col_row_height += col_row.col_row_height_ratio; + col_row_height += col_row.constraint.ratio(); } if is_valid_col { row_mapping.insert( ( row_width * 100 / row.total_col_ratio, - (row_width + col.col_width_ratio) * 100 / row.total_col_ratio, + (row_width + col.constraint.ratio()) * 100 / row.total_col_ratio, ), (row.total_col_ratio, col_mapping), ); is_valid_row = true; } - row_width += col.col_width_ratio; + row_width += col.constraint.ratio(); } if is_valid_row { layout_mapping.insert( ( total_height * 100 / self.total_row_height_ratio, - (total_height + row.row_height_ratio) * 100 / self.total_row_height_ratio, + (total_height + row.constraint.ratio()) * 100 / self.total_row_height_ratio, ), (self.total_row_height_ratio, row_mapping), ); } - total_height += row.row_height_ratio; + total_height += row.constraint.ratio(); } // Now pass through a second time; this time we want to build up @@ -121,20 +121,20 @@ impl BottomLayout { let mut col_cursor = 0; let row_height_percentage_start = height_cursor * 100 / self.total_row_height_ratio; let row_height_percentage_end = - (height_cursor + row.row_height_ratio) * 100 / self.total_row_height_ratio; + (height_cursor + row.constraint.ratio()) * 100 / self.total_row_height_ratio; for col in &mut row.children { let mut col_row_cursor = 0; let col_width_percentage_start = col_cursor * 100 / row.total_col_ratio; let col_width_percentage_end = - (col_cursor + col.col_width_ratio) * 100 / row.total_col_ratio; + (col_cursor + col.constraint.ratio()) * 100 / row.total_col_ratio; for col_row in &mut col.children { let mut widget_cursor = 0; let col_row_height_percentage_start = col_row_cursor * 100 / col.total_col_row_ratio; let col_row_height_percentage_end = - (col_row_cursor + col_row.col_row_height_ratio) * 100 + (col_row_cursor + col_row.constraint.ratio()) * 100 / col.total_col_row_ratio; let col_row_children_len = col_row.children.len(); @@ -147,7 +147,8 @@ impl BottomLayout { let widget_width_percentage_start = widget_cursor * 100 / col_row.total_widget_ratio; let widget_width_percentage_end = - (widget_cursor + widget.width_ratio) * 100 / col_row.total_widget_ratio; + (widget_cursor + widget.constraint.ratio()) * 100 + / col_row.total_widget_ratio; if let Some(current_row) = layout_mapping .get(&(row_height_percentage_start, row_height_percentage_end)) @@ -523,91 +524,85 @@ impl BottomLayout { } } } - widget_cursor += widget.width_ratio; + widget_cursor += widget.constraint.ratio(); } - col_row_cursor += col_row.col_row_height_ratio; + col_row_cursor += col_row.constraint.ratio(); } - col_cursor += col.col_width_ratio; + col_cursor += col.constraint.ratio(); } - height_cursor += row.row_height_ratio; + height_cursor += row.constraint.ratio(); } } pub fn init_basic_default(use_battery: bool) -> Self { let table_widgets = if use_battery { let disk_widget = BottomWidget::new(BottomWidgetType::Disk, 4) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .left_neighbour(Some(8)) .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)); let proc_sort = BottomWidget::new(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) .left_neighbour(Some(4)) .right_neighbour(Some(DEFAULT_WIDGET_ID)) - .width_ratio(1) + .ratio(1) .parent_reflector(Some((WidgetDirection::Right, 2))); let proc = BottomWidget::new(BottomWidgetType::Proc, DEFAULT_WIDGET_ID) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) .right_neighbour(Some(7)) - .width_ratio(2); + .ratio(2); let proc_search = BottomWidget::new(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(DEFAULT_WIDGET_ID)) .left_neighbour(Some(4)) .right_neighbour(Some(7)) .parent_reflector(Some((WidgetDirection::Up, 1))); let temp = BottomWidget::new(BottomWidgetType::Temp, 7) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .left_neighbour(Some(DEFAULT_WIDGET_ID)) .right_neighbour(Some(8)); let battery = BottomWidget::new(BottomWidgetType::Battery, 8) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .left_neighbour(Some(7)) .right_neighbour(Some(4)); vec![ - BottomCol::new(vec![ - BottomColRow::new(vec![disk_widget]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), + BottomCol::new(vec![BottomColRow::new(vec![disk_widget]).canvas_handled()]) + .canvas_handled(), BottomCol::new(vec![ BottomColRow::new(vec![proc_sort, proc]) - .canvas_handle_height(true) + .canvas_handled() .total_widget_ratio(3), - BottomColRow::new(vec![proc_search]).canvas_handle_height(true), + BottomColRow::new(vec![proc_search]).canvas_handled(), ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![temp]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![battery]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), + .canvas_handled(), + BottomCol::new(vec![BottomColRow::new(vec![temp]).canvas_handled()]) + .canvas_handled(), + BottomCol::new(vec![BottomColRow::new(vec![battery]).canvas_handled()]) + .canvas_handled(), ] } else { let disk = BottomWidget::new(BottomWidgetType::Disk, 4) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .left_neighbour(Some(7)) .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)); let proc_sort = BottomWidget::new(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) .left_neighbour(Some(4)) @@ -615,7 +610,7 @@ impl BottomLayout { .parent_reflector(Some((WidgetDirection::Right, 2))); let proc = BottomWidget::new(BottomWidgetType::Proc, DEFAULT_WIDGET_ID) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) @@ -623,110 +618,111 @@ impl BottomLayout { let proc_search = BottomWidget::new(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(DEFAULT_WIDGET_ID)) .left_neighbour(Some(4)) .right_neighbour(Some(7)) .parent_reflector(Some((WidgetDirection::Up, 1))); let temp = BottomWidget::new(BottomWidgetType::Temp, 7) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(100)) .left_neighbour(Some(DEFAULT_WIDGET_ID)) .right_neighbour(Some(4)); vec![ + BottomCol::new(vec![BottomColRow::new(vec![disk]).canvas_handled()]) + .canvas_handled(), BottomCol::new(vec![ - BottomColRow::new(vec![disk]).canvas_handle_height(true) + BottomColRow::new(vec![proc_sort, proc]).canvas_handled(), + BottomColRow::new(vec![proc_search]).canvas_handled(), ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![proc_sort, proc]).canvas_handle_height(true), - BottomColRow::new(vec![proc_search]).canvas_handle_height(true), - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![temp]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), + .canvas_handled(), + BottomCol::new(vec![BottomColRow::new(vec![temp]).canvas_handled()]) + .canvas_handled(), ] }; let cpu = BottomWidget::new(BottomWidgetType::BasicCpu, 1) - .canvas_handle_width(true) + .canvas_handled() .down_neighbour(Some(2)); let mem = BottomWidget::new(BottomWidgetType::BasicMem, 2) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(1)) .down_neighbour(Some(100)) .right_neighbour(Some(3)); let net = BottomWidget::new(BottomWidgetType::BasicNet, 3) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(1)) .down_neighbour(Some(100)) .left_neighbour(Some(2)); let table = BottomWidget::new(BottomWidgetType::BasicTables, 100) - .canvas_handle_width(true) + .canvas_handled() .up_neighbour(Some(2)); BottomLayout { total_row_height_ratio: 3, rows: vec![ BottomRow::new(vec![BottomCol::new(vec![ - BottomColRow::new(vec![cpu]).canvas_handle_height(true) + BottomColRow::new(vec![cpu]).canvas_handled() ]) - .canvas_handle_width(true)]) - .canvas_handle_height(true), + .canvas_handled()]) + .canvas_handled(), BottomRow::new(vec![BottomCol::new(vec![BottomColRow::new(vec![ mem, net, ]) - .canvas_handle_height(true)]) - .canvas_handle_width(true)]) - .canvas_handle_height(true), + .canvas_handled()]) + .canvas_handled()]) + .canvas_handled(), BottomRow::new(vec![BottomCol::new(vec![ - BottomColRow::new(vec![table]).canvas_handle_height(true) + BottomColRow::new(vec![table]).canvas_handled() ]) - .canvas_handle_width(true)]) - .canvas_handle_height(true), - BottomRow::new(table_widgets).canvas_handle_height(true), + .canvas_handled()]) + .canvas_handled(), + BottomRow::new(table_widgets).canvas_handled(), ], } } } -// pub enum BottomLayoutNode { -// Container(BottomContainer), -// Widget(BottomWidget), -// } +#[derive(Clone, Debug)] +pub enum IntermediaryConstraint { + PartialRatio(u32), + CanvasHandled { ratio: Option }, + Grow { minimum: Option }, +} -// pub struct BottomContainer { -// children: Vec, -// root_ratio: u32, -// growth_type: BottomLayoutNodeSizing, -// } +impl Default for IntermediaryConstraint { + fn default() -> Self { + IntermediaryConstraint::PartialRatio(1) + } +} -// pub enum BottomContainerType { -// Row, -// Col, -// } - -// pub enum BottomLayoutNodeSizing { -// Ratio(u32), -// CanvasHandles, -// FlexGrow, -// } +impl IntermediaryConstraint { + pub fn ratio(&self) -> u32 { + match self { + IntermediaryConstraint::PartialRatio(val) => *val, + IntermediaryConstraint::Grow { minimum } => match minimum { + Some(val) => *val, + None => 1, + }, + IntermediaryConstraint::CanvasHandled { ratio } => match ratio { + Some(val) => *val, + None => 1, + }, + } + } +} /// Represents a single row in the layout. #[derive(Clone, Debug)] pub struct BottomRow { pub children: Vec, pub total_col_ratio: u32, - pub row_height_ratio: u32, - pub canvas_handle_height: bool, - pub flex_grow: bool, + pub constraint: IntermediaryConstraint, } impl BottomRow { @@ -734,9 +730,7 @@ impl BottomRow { Self { children, total_col_ratio: 1, - row_height_ratio: 1, - canvas_handle_height: false, - flex_grow: false, + constraint: IntermediaryConstraint::default(), } } @@ -745,18 +739,18 @@ impl BottomRow { self } - pub fn row_height_ratio(mut self, row_height_ratio: u32) -> Self { - self.row_height_ratio = row_height_ratio; + pub fn ratio(mut self, row_height_ratio: u32) -> Self { + self.constraint = IntermediaryConstraint::PartialRatio(row_height_ratio); self } - pub fn canvas_handle_height(mut self, canvas_handle_height: bool) -> Self { - self.canvas_handle_height = canvas_handle_height; + pub fn canvas_handled(mut self) -> Self { + self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; self } - pub fn flex_grow(mut self, flex_grow: bool) -> Self { - self.flex_grow = flex_grow; + pub fn grow(mut self, minimum: Option) -> Self { + self.constraint = IntermediaryConstraint::Grow { minimum }; self } } @@ -768,9 +762,7 @@ impl BottomRow { pub struct BottomCol { pub children: Vec, pub total_col_row_ratio: u32, - pub col_width_ratio: u32, - pub canvas_handle_width: bool, - pub flex_grow: bool, + pub constraint: IntermediaryConstraint, } impl BottomCol { @@ -778,9 +770,7 @@ impl BottomCol { Self { children, total_col_row_ratio: 1, - col_width_ratio: 1, - canvas_handle_width: false, - flex_grow: false, + constraint: IntermediaryConstraint::default(), } } @@ -789,18 +779,18 @@ impl BottomCol { self } - pub fn col_width_ratio(mut self, col_width_ratio: u32) -> Self { - self.col_width_ratio = col_width_ratio; + pub fn ratio(mut self, col_width_ratio: u32) -> Self { + self.constraint = IntermediaryConstraint::PartialRatio(col_width_ratio); self } - pub fn canvas_handle_width(mut self, canvas_handle_width: bool) -> Self { - self.canvas_handle_width = canvas_handle_width; + pub fn canvas_handled(mut self) -> Self { + self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; self } - pub fn flex_grow(mut self, flex_grow: bool) -> Self { - self.flex_grow = flex_grow; + pub fn grow(mut self, minimum: Option) -> Self { + self.constraint = IntermediaryConstraint::Grow { minimum }; self } } @@ -809,9 +799,7 @@ impl BottomCol { pub struct BottomColRow { pub children: Vec, pub total_widget_ratio: u32, - pub col_row_height_ratio: u32, - pub canvas_handle_height: bool, - pub flex_grow: bool, + pub constraint: IntermediaryConstraint, } impl BottomColRow { @@ -819,9 +807,7 @@ impl BottomColRow { Self { children, total_widget_ratio: 1, - col_row_height_ratio: 1, - canvas_handle_height: false, - flex_grow: false, + constraint: IntermediaryConstraint::default(), } } @@ -830,18 +816,18 @@ impl BottomColRow { self } - pub(crate) fn col_row_height_ratio(mut self, col_row_height_ratio: u32) -> Self { - self.col_row_height_ratio = col_row_height_ratio; + pub fn ratio(mut self, col_row_height_ratio: u32) -> Self { + self.constraint = IntermediaryConstraint::PartialRatio(col_row_height_ratio); self } - pub(crate) fn canvas_handle_height(mut self, canvas_handle_height: bool) -> Self { - self.canvas_handle_height = canvas_handle_height; + pub fn canvas_handled(mut self) -> Self { + self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; self } - pub(crate) fn flex_grow(mut self, flex_grow: bool) -> Self { - self.flex_grow = flex_grow; + pub fn grow(mut self, minimum: Option) -> Self { + self.constraint = IntermediaryConstraint::Grow { minimum }; self } } @@ -872,18 +858,12 @@ impl WidgetDirection { pub struct BottomWidget { pub widget_type: BottomWidgetType, pub widget_id: u64, - pub width_ratio: u32, + pub constraint: IntermediaryConstraint, pub left_neighbour: Option, pub right_neighbour: Option, pub up_neighbour: Option, pub down_neighbour: Option, - /// If set to true, the canvas will override any ratios. - pub canvas_handle_width: bool, - - /// Whether we want this widget to take up all available room (and ignore any ratios). - pub flex_grow: bool, - /// The value is the direction to bounce, as well as the parent offset. pub parent_reflector: Option<(WidgetDirection, u64)>, @@ -899,24 +879,17 @@ impl BottomWidget { Self { widget_type, widget_id, - width_ratio: 1, + constraint: IntermediaryConstraint::default(), left_neighbour: None, right_neighbour: None, up_neighbour: None, down_neighbour: None, - canvas_handle_width: false, - flex_grow: false, parent_reflector: None, top_left_corner: None, bottom_right_corner: None, } } - pub(crate) fn width_ratio(mut self, width_ratio: u32) -> Self { - self.width_ratio = width_ratio; - self - } - pub(crate) fn left_neighbour(mut self, left_neighbour: Option) -> Self { self.left_neighbour = left_neighbour; self @@ -937,13 +910,23 @@ impl BottomWidget { self } - pub(crate) fn canvas_handle_width(mut self, canvas_handle_width: bool) -> Self { - self.canvas_handle_width = canvas_handle_width; + pub(crate) fn ratio(mut self, width_ratio: u32) -> Self { + self.constraint = IntermediaryConstraint::PartialRatio(width_ratio); self } - pub(crate) fn flex_grow(mut self, flex_grow: bool) -> Self { - self.flex_grow = flex_grow; + pub fn canvas_handled(mut self) -> Self { + self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; + self + } + + pub fn canvas_with_ratio(mut self, ratio: u32) -> Self { + self.constraint = IntermediaryConstraint::CanvasHandled { ratio: Some(ratio) }; + self + } + + pub fn grow(mut self, minimum: Option) -> Self { + self.constraint = IntermediaryConstraint::Grow { minimum }; self } @@ -1037,6 +1020,8 @@ Supported widget names: | disk | +--------------------------+ | batt, battery | ++--------------------------+ +| empty | +--------------------------+ ", ))) @@ -1059,6 +1044,8 @@ Supported widget names: | temp, temperature | +--------------------------+ | disk | ++--------------------------+ +| empty | +--------------------------+ ", ))) diff --git a/src/canvas.rs b/src/canvas.rs index 3a02803b..0f3e7202 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -18,7 +18,7 @@ use tui::{ use crate::{ app::{ - layout_manager::{BottomColRow, BottomLayout, BottomWidgetType}, + layout_manager::{BottomColRow, BottomLayout, BottomWidgetType, IntermediaryConstraint}, App, }, constants::*, @@ -80,7 +80,7 @@ pub enum LayoutConstraint { } impl Painter { - pub fn init(widget_layout: BottomLayout, styling: CanvasStyling) -> anyhow::Result { + pub fn init(layout: BottomLayout, styling: CanvasStyling) -> anyhow::Result { // Now for modularity; we have to also initialize the base layouts! // We want to do this ONCE and reuse; after this we can just construct // based on the console size. @@ -90,56 +90,69 @@ impl Painter { let mut col_row_constraints = Vec::new(); let mut layout_constraints = Vec::new(); - widget_layout.rows.iter().for_each(|row| { - if row.canvas_handle_height { - row_constraints.push(LayoutConstraint::CanvasHandled); - } else { - row_constraints.push(LayoutConstraint::Ratio( - row.row_height_ratio, - widget_layout.total_row_height_ratio, - )); + layout.rows.iter().for_each(|row| { + match row.constraint { + IntermediaryConstraint::PartialRatio(val) => { + row_constraints + .push(LayoutConstraint::Ratio(val, layout.total_row_height_ratio)); + } + IntermediaryConstraint::CanvasHandled { .. } => { + row_constraints.push(LayoutConstraint::CanvasHandled); + } + IntermediaryConstraint::Grow { .. } => { + row_constraints.push(LayoutConstraint::Grow); + } } let mut new_col_constraints = Vec::new(); let mut new_widget_constraints = Vec::new(); let mut new_col_row_constraints = Vec::new(); row.children.iter().for_each(|col| { - if col.canvas_handle_width { - new_col_constraints.push(LayoutConstraint::CanvasHandled); - } else { - new_col_constraints.push(LayoutConstraint::Ratio( - col.col_width_ratio, - row.total_col_ratio, - )); + match col.constraint { + IntermediaryConstraint::PartialRatio(val) => { + new_col_constraints.push(LayoutConstraint::Ratio(val, row.total_col_ratio)); + } + IntermediaryConstraint::CanvasHandled { .. } => { + new_col_constraints.push(LayoutConstraint::CanvasHandled); + } + IntermediaryConstraint::Grow { .. } => { + new_col_constraints.push(LayoutConstraint::Grow); + } } let mut new_new_col_row_constraints = Vec::new(); let mut new_new_widget_constraints = Vec::new(); col.children.iter().for_each(|col_row| { - if col_row.canvas_handle_height { - new_new_col_row_constraints.push(LayoutConstraint::CanvasHandled); - } else if col_row.flex_grow { - new_new_col_row_constraints.push(LayoutConstraint::Grow); - } else { - new_new_col_row_constraints.push(LayoutConstraint::Ratio( - col_row.col_row_height_ratio, - col.total_col_row_ratio, - )); + match col_row.constraint { + IntermediaryConstraint::PartialRatio(val) => { + new_new_col_row_constraints + .push(LayoutConstraint::Ratio(val, col.total_col_row_ratio)); + } + IntermediaryConstraint::CanvasHandled { .. } => { + new_new_col_row_constraints.push(LayoutConstraint::CanvasHandled); + } + IntermediaryConstraint::Grow { .. } => { + new_new_col_row_constraints.push(LayoutConstraint::Grow); + } } let mut new_new_new_widget_constraints = Vec::new(); - col_row.children.iter().for_each(|widget| { - if widget.canvas_handle_width { - new_new_new_widget_constraints.push(LayoutConstraint::CanvasHandled); - } else if widget.flex_grow { - new_new_new_widget_constraints.push(LayoutConstraint::Grow); - } else { - new_new_new_widget_constraints.push(LayoutConstraint::Ratio( - widget.width_ratio, - col_row.total_widget_ratio, - )); - } - }); + col_row + .children + .iter() + .for_each(|widget| match widget.constraint { + IntermediaryConstraint::PartialRatio(val) => { + new_new_new_widget_constraints + .push(LayoutConstraint::Ratio(val, col_row.total_widget_ratio)); + } + IntermediaryConstraint::CanvasHandled { .. } => { + new_new_new_widget_constraints + .push(LayoutConstraint::CanvasHandled); + } + IntermediaryConstraint::Grow { .. } => { + new_new_new_widget_constraints.push(LayoutConstraint::Grow); + } + }); new_new_widget_constraints.push(new_new_new_widget_constraints); }); new_col_row_constraints.push(new_new_col_row_constraints); @@ -158,7 +171,7 @@ impl Painter { col_constraints, col_row_constraints, layout_constraints, - widget_layout, + widget_layout: layout, derived_widget_draw_locs: Vec::default(), }; diff --git a/src/options.rs b/src/options.rs index 86d44e75..95c780f6 100644 --- a/src/options.rs +++ b/src/options.rs @@ -747,14 +747,6 @@ fn get_default_widget_and_count( fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool { #[cfg(feature = "battery")] { - if matches.get_flag("battery") { - return true; - } else if let Some(flags) = &config.flags { - if let Some(battery) = flags.battery { - return battery; - } - } - if let Ok(battery_manager) = Manager::new() { if let Ok(batteries) = battery_manager.batteries() { if batteries.count() == 0 { @@ -762,6 +754,14 @@ fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool { } } } + + if matches.get_flag("battery") { + return true; + } else if let Some(flags) = &config.flags { + if let Some(battery) = flags.battery { + return battery; + } + } } false diff --git a/src/options/config/layout.rs b/src/options/config/layout.rs index 4b3e587a..45c2a0ab 100644 --- a/src/options/config/layout.rs +++ b/src/options/config/layout.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{app::layout_manager::*, error::Result}; -/// Represents a row. This has a length of some sort (optional) and a vector +/// Represents a row. This has a length of some sort (optional) and a vector /// of children. #[derive(Clone, Deserialize, Debug, Serialize)] #[serde(rename = "row")] @@ -19,21 +19,15 @@ fn new_cpu(left_legend: bool, iter_id: &mut u64) -> BottomColRow { if left_legend { BottomColRow::new(vec![ BottomWidget::new(BottomWidgetType::CpuLegend, legend_id) - .width_ratio(3) - .canvas_handle_width(true) + .canvas_with_ratio(3) .parent_reflector(Some((WidgetDirection::Right, 1))), - BottomWidget::new(BottomWidgetType::Cpu, cpu_id) - .width_ratio(17) - .flex_grow(true), + BottomWidget::new(BottomWidgetType::Cpu, cpu_id).grow(Some(17)), ]) } else { BottomColRow::new(vec![ - BottomWidget::new(BottomWidgetType::Cpu, cpu_id) - .width_ratio(17) - .flex_grow(true), + BottomWidget::new(BottomWidgetType::Cpu, cpu_id).grow(Some(17)), BottomWidget::new(BottomWidgetType::CpuLegend, legend_id) - .width_ratio(3) - .canvas_handle_width(true) + .canvas_with_ratio(3) .parent_reflector(Some((WidgetDirection::Left, 1))), ]) } @@ -42,13 +36,12 @@ fn new_cpu(left_legend: bool, iter_id: &mut u64) -> BottomColRow { fn new_proc_sort(sort_id: u64) -> BottomWidget { BottomWidget::new(BottomWidgetType::ProcSort, sort_id) - .canvas_handle_width(true) + .canvas_handled() .parent_reflector(Some((WidgetDirection::Right, 2))) - .width_ratio(1) } fn new_proc(proc_id: u64) -> BottomWidget { - BottomWidget::new(BottomWidgetType::Proc, proc_id).width_ratio(2) + BottomWidget::new(BottomWidgetType::Proc, proc_id).ratio(2) } fn new_proc_search(search_id: u64) -> BottomWidget { @@ -99,7 +92,7 @@ impl Row { children.push(match widget_type { BottomWidgetType::Cpu => { BottomCol::new(vec![new_cpu(left_legend, iter_id)]) - .col_width_ratio(width_ratio) + .ratio(width_ratio) } BottomWidgetType::Proc => { let proc_id = *iter_id; @@ -110,34 +103,31 @@ impl Row { new_proc_sort(*iter_id), new_proc(proc_id), ]) - .total_widget_ratio(3) - .flex_grow(true), + .grow(None) + .total_widget_ratio(3), BottomColRow::new(vec![new_proc_search(proc_search_id)]) - .canvas_handle_height(true), + .canvas_handled(), ]) .total_col_row_ratio(2) - .col_width_ratio(width_ratio) + .ratio(width_ratio) } _ => BottomCol::new(vec![BottomColRow::new(vec![BottomWidget::new( widget_type, *iter_id, )])]) - .col_width_ratio(width_ratio), + .ratio(width_ratio), }); } RowChildren::Col { ratio, child } => { let col_width_ratio = ratio.unwrap_or(1); total_col_ratio += col_width_ratio; let mut total_col_row_ratio = 0; - let mut contains_proc = false; let mut col_row_children: Vec = Vec::new(); for widget in child { let widget_type = widget.widget_type.parse::()?; *iter_id += 1; - let col_row_height_ratio = widget.ratio.unwrap_or(1); - total_col_row_ratio += col_row_height_ratio; if let Some(default_widget_type_val) = default_widget_type { if *default_widget_type_val == widget_type @@ -159,13 +149,17 @@ impl Row { match widget_type { BottomWidgetType::Cpu => { + let col_row_height_ratio = widget.ratio.unwrap_or(1); + total_col_row_ratio += col_row_height_ratio; + col_row_children.push( - new_cpu(left_legend, iter_id) - .col_row_height_ratio(col_row_height_ratio), + new_cpu(left_legend, iter_id).ratio(col_row_height_ratio), ); } BottomWidgetType::Proc => { - contains_proc = true; + let col_row_height_ratio = widget.ratio.unwrap_or(1) + 1; + total_col_row_ratio += col_row_height_ratio; + let proc_id = *iter_id; let proc_search_id = *iter_id + 1; *iter_id += 2; @@ -174,35 +168,25 @@ impl Row { new_proc_sort(*iter_id), new_proc(proc_id), ]) - .col_row_height_ratio(col_row_height_ratio) + .ratio(col_row_height_ratio) .total_widget_ratio(3), ); col_row_children.push( BottomColRow::new(vec![new_proc_search(proc_search_id)]) - .canvas_handle_height(true) - .col_row_height_ratio(col_row_height_ratio), + .canvas_handled(), ); } - _ => col_row_children.push( - BottomColRow::new(vec![BottomWidget::new( - widget_type, - *iter_id, - )]) - .col_row_height_ratio(col_row_height_ratio), - ), - } - } + _ => { + let col_row_height_ratio = widget.ratio.unwrap_or(1); + total_col_row_ratio += col_row_height_ratio; - if contains_proc { - // Must adjust ratios to work with proc - total_col_row_ratio *= 2; - for child in &mut col_row_children { - // Multiply all non-proc or proc-search ratios by 2 - if !child.children.is_empty() { - match child.children[0].widget_type { - BottomWidgetType::ProcSearch => {} - _ => child.col_row_height_ratio *= 2, - } + col_row_children.push( + BottomColRow::new(vec![BottomWidget::new( + widget_type, + *iter_id, + )]) + .ratio(col_row_height_ratio), + ) } } } @@ -210,7 +194,7 @@ impl Row { children.push( BottomCol::new(col_row_children) .total_col_row_ratio(total_col_row_ratio) - .col_width_ratio(col_width_ratio), + .ratio(col_width_ratio), ); } } @@ -219,7 +203,7 @@ impl Row { Ok(BottomRow::new(children) .total_col_ratio(total_col_ratio) - .row_height_ratio(row_ratio)) + .ratio(row_ratio)) } } diff --git a/tests/arg_tests.rs b/tests/integration/arg_tests.rs similarity index 76% rename from tests/arg_tests.rs rename to tests/integration/arg_tests.rs index af4df7b8..13ebc5f0 100644 --- a/tests/arg_tests.rs +++ b/tests/integration/arg_tests.rs @@ -1,15 +1,13 @@ //! These tests are mostly here just to ensure that invalid results will be caught when passing arguments. -mod util; use assert_cmd::prelude::*; use predicates::prelude::*; -use util::*; + +use crate::util::{btm_command, no_cfg_btm_command}; #[test] fn test_small_rate() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + btm_command(&["-C", "./tests/valid_configs/empty_config.toml"]) .arg("-r") .arg("249") .assert() @@ -21,9 +19,7 @@ fn test_small_rate() { #[test] fn test_large_default_time() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-t") .arg("18446744073709551616") .assert() @@ -33,9 +29,7 @@ fn test_large_default_time() { #[test] fn test_small_default_time() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-t") .arg("900") .assert() @@ -47,9 +41,7 @@ fn test_small_default_time() { #[test] fn test_large_delta_time() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-d") .arg("18446744073709551616") .assert() @@ -59,9 +51,7 @@ fn test_large_delta_time() { #[test] fn test_small_delta_time() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-d") .arg("900") .assert() @@ -73,9 +63,7 @@ fn test_small_delta_time() { #[test] fn test_large_rate() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-r") .arg("18446744073709551616") .assert() @@ -86,9 +74,7 @@ fn test_large_rate() { #[test] fn test_negative_rate() { // This test should auto fail due to how clap works - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-r") .arg("-1000") .assert() @@ -98,9 +84,7 @@ fn test_negative_rate() { #[test] fn test_invalid_rate() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-r") .arg("100-1000") .assert() @@ -110,9 +94,7 @@ fn test_invalid_rate() { #[test] fn test_conflicting_temps() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("-c") .arg("-f") .assert() @@ -122,9 +104,7 @@ fn test_conflicting_temps() { #[test] fn test_invalid_default_widget_1() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("--default_widget_type") .arg("fake_widget") .assert() @@ -134,9 +114,7 @@ fn test_invalid_default_widget_1() { #[test] fn test_invalid_default_widget_2() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("--default_widget_type") .arg("cpu") .arg("--default_widget_count") @@ -150,9 +128,7 @@ fn test_invalid_default_widget_2() { #[test] fn test_missing_default_widget_type() { - btm_command() - .arg("-C") - .arg("./tests/empty_config.toml") + no_cfg_btm_command() .arg("--default_widget_count") .arg("3") .assert() @@ -165,7 +141,7 @@ fn test_missing_default_widget_type() { #[test] #[cfg_attr(feature = "battery", ignore)] fn test_battery_flag() { - btm_command() + no_cfg_btm_command() .arg("--battery") .assert() .failure() @@ -177,7 +153,7 @@ fn test_battery_flag() { #[test] #[cfg_attr(feature = "gpu", ignore)] fn test_gpu_flag() { - btm_command() + no_cfg_btm_command() .arg("--enable_gpu") .assert() .failure() diff --git a/tests/invalid_config_tests.rs b/tests/integration/invalid_config_tests.rs similarity index 53% rename from tests/invalid_config_tests.rs rename to tests/integration/invalid_config_tests.rs index 62e2a376..92a67eac 100644 --- a/tests/invalid_config_tests.rs +++ b/tests/integration/invalid_config_tests.rs @@ -1,16 +1,13 @@ -mod util; +//! These tests are for testing some invalid config-file-specific options. use assert_cmd::prelude::*; use predicates::prelude::*; -use util::*; -// These tests are for testing some config file-specific options. +use crate::util::btm_command; #[test] fn test_toml_mismatch_type() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/toml_mismatch_type.toml") + btm_command(&["-C", "./tests/invalid_configs/toml_mismatch_type.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid type")); @@ -18,9 +15,7 @@ fn test_toml_mismatch_type() { #[test] fn test_empty_layout() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/empty_layout.toml") + btm_command(&["-C", "./tests/invalid_configs/empty_layout.toml"]) .assert() .failure() .stderr(predicate::str::contains("at least one widget")); @@ -28,21 +23,20 @@ fn test_empty_layout() { #[test] fn test_invalid_layout_widget_type() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_layout_widget_type.toml") - .assert() - .failure() - .stderr(predicate::str::contains("invalid widget name")); + btm_command(&[ + "-C", + "./tests/invalid_configs/invalid_layout_widget_type.toml", + ]) + .assert() + .failure() + .stderr(predicate::str::contains("invalid widget name")); } /// This test isn't really needed as this is technically covered by TOML spec. /// However, I feel like it's worth checking anyways - not like it takes long. #[test] fn test_duplicate_temp_type() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/duplicate_temp_type.toml") + btm_command(&["-C", "./tests/invalid_configs/duplicate_temp_type.toml"]) .assert() .failure() .stderr(predicate::str::contains("duplicate key")); @@ -51,9 +45,7 @@ fn test_duplicate_temp_type() { /// Checks for if a hex is valid #[test] fn test_invalid_colour_hex() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_hex.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_hex.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid hex color")); @@ -62,9 +54,7 @@ fn test_invalid_colour_hex() { /// Checks for if a hex is too long #[test] fn test_invalid_colour_hex_2() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_hex_2.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_hex_2.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid hex color")); @@ -74,9 +64,7 @@ fn test_invalid_colour_hex_2() { /// boundary errors! #[test] fn test_invalid_colour_hex_3() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_hex_3.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_hex_3.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid hex color")); @@ -84,9 +72,7 @@ fn test_invalid_colour_hex_3() { #[test] fn test_invalid_colour_name() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_name.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_name.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid named color")); @@ -94,9 +80,7 @@ fn test_invalid_colour_name() { #[test] fn test_invalid_colour_rgb() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_rgb.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_rgb.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid RGB")); @@ -104,9 +88,7 @@ fn test_invalid_colour_rgb() { #[test] fn test_invalid_colour_rgb_2() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_rgb_2.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_rgb_2.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid RGB")); @@ -114,9 +96,7 @@ fn test_invalid_colour_rgb_2() { #[test] fn test_invalid_colour_string() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_colour_string.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_colour_string.toml"]) .assert() .failure() .stderr(predicate::str::contains("invalid named color")); @@ -124,29 +104,29 @@ fn test_invalid_colour_string() { #[test] fn test_lone_default_widget_count() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/lone_default_widget_count.toml") - .assert() - .failure() - .stderr(predicate::str::contains("it must be used with")); + btm_command(&[ + "-C", + "./tests/invalid_configs/lone_default_widget_count.toml", + ]) + .assert() + .failure() + .stderr(predicate::str::contains("it must be used with")); } #[test] fn test_invalid_default_widget_count() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_default_widget_count.toml") - .assert() - .failure() - .stderr(predicate::str::contains("number too large")); + btm_command(&[ + "-C", + "./tests/invalid_configs/invalid_default_widget_count.toml", + ]) + .assert() + .failure() + .stderr(predicate::str::contains("number too large")); } #[test] fn test_invalid_process_column() { - btm_command() - .arg("-C") - .arg("./tests/invalid_configs/invalid_process_column.toml") + btm_command(&["-C", "./tests/invalid_configs/invalid_process_column.toml"]) .assert() .failure() .stderr(predicate::str::contains("doesn't match")); diff --git a/tests/layout_management_tests.rs b/tests/integration/layout_management_tests.rs similarity index 100% rename from tests/layout_management_tests.rs rename to tests/integration/layout_management_tests.rs diff --git a/tests/layout_movement_tests.rs b/tests/integration/layout_movement_tests.rs similarity index 100% rename from tests/layout_movement_tests.rs rename to tests/integration/layout_movement_tests.rs diff --git a/tests/integration/main.rs b/tests/integration/main.rs new file mode 100644 index 00000000..01c3f9f5 --- /dev/null +++ b/tests/integration/main.rs @@ -0,0 +1,11 @@ +//! Integration tests for bottom. + +mod util; + +mod arg_tests; +mod invalid_config_tests; +mod layout_management_tests; +mod layout_movement_tests; + +#[cfg(all(target_arch = "x86_64", target_os = "linux"))] +mod valid_config_tests; diff --git a/tests/integration/util.rs b/tests/integration/util.rs new file mode 100644 index 00000000..e1ee1ffd --- /dev/null +++ b/tests/integration/util.rs @@ -0,0 +1,144 @@ +use std::{env, ffi::OsString, path::Path, process::Command}; + +use hashbrown::HashMap; + +#[cfg(all(target_arch = "x86_64", target_os = "linux"))] +use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize}; + +pub fn abs_path(path: &str) -> OsString { + Path::new(path).canonicalize().unwrap().into_os_string() +} + +/// Returns a QEMU runner target given an architecture. +fn get_qemu_target(arch: &str) -> &str { + match arch { + "armv7" => "arm", + "i686" => "i386", + "powerpc" => "ppc", + "powerpc64le" => "ppc64le", + _ => arch, + } +} + +/// This is required since running binary tests via cross can cause be tricky! We need to basically "magically" grab +/// the correct runner in some cases, which can be done by inspecting env variables that should only show up while +/// using cross. +/// +/// Originally inspired by [ripgrep's test files](https://cs.github.com/BurntSushi/ripgrep/blob/9f0e88bcb14e02da1b88872435b17d74786640b5/tests/util.rs#L470), +/// but adapted to work more generally with the architectures supported by bottom after looking through cross' +/// [linux-runner](https://github.com/cross-rs/cross/blob/main/docker/linux-runner) file. +fn cross_runner() -> Option { + const TARGET_RUNNER: &str = "CARGO_TARGET_RUNNER"; + const CROSS_RUNNER: &str = "CROSS_RUNNER"; + + let env_mapping = env::vars_os() + .filter_map(|(k, v)| { + let (k, v) = (k.to_string_lossy(), v.to_string_lossy()); + + if k.starts_with("CARGO_TARGET_") && k.ends_with("_RUNNER") && !v.is_empty() { + Some((TARGET_RUNNER.to_string(), v.to_string())) + } else if k == CROSS_RUNNER && !v.is_empty() { + Some((k.to_string(), v.to_string())) + } else { + None + } + }) + .collect::>(); + + if let Some(cross_runner) = env_mapping.get(CROSS_RUNNER) { + if cross_runner == "qemu-user" { + env_mapping.get(TARGET_RUNNER).map(|target_runner| { + format!( + "qemu-{}", + get_qemu_target(target_runner.split_ascii_whitespace().last().unwrap()) + ) + }) + } else { + None + } + } else { + env_mapping.get(TARGET_RUNNER).cloned() + } +} + +const BTM_EXE_PATH: &str = env!("CARGO_BIN_EXE_btm"); +const RUNNER_ENV_VARS: [(&str, &str); 1] = [("NO_COLOR", "1")]; +const DEFAULT_CFG: [&str; 2] = ["-C", "./tests/valid_configs/empty_config.toml"]; + +/// Returns the [`Command`] of a binary invocation of bottom, alongside +/// any required env variables. +pub fn btm_command(args: &[&str]) -> Command { + let mut cmd = match cross_runner() { + None => Command::new(BTM_EXE_PATH), + Some(runner) => { + let mut cmd = Command::new(runner); + cmd.envs(RUNNER_ENV_VARS); + cmd.arg(BTM_EXE_PATH); + cmd + } + }; + + let mut prev = ""; + for arg in args.iter() { + if prev == "-C" { + // This is the config file; make sure we set it to absolute path! + cmd.arg(abs_path(arg)); + } else { + cmd.arg(arg); + } + + prev = arg; + } + + cmd +} + +/// Returns the [`Command`] of a binary invocation of bottom, alongside +/// any required env variables, and with the default, empty config file. +pub fn no_cfg_btm_command() -> Command { + btm_command(&DEFAULT_CFG) +} + +/// Spawns `btm` in a pty, returning the pair alongside a handle to the child. +#[cfg(all(target_arch = "x86_64", target_os = "linux"))] +pub fn spawn_btm_in_pty(args: &[&str]) -> (Box, Box) { + let native_pty = native_pty_system(); + + let pair = native_pty + .openpty(PtySize { + rows: 100, + cols: 100, + pixel_width: 1, + pixel_height: 1, + }) + .unwrap(); + + let btm_exe = BTM_EXE_PATH; + let mut cmd = match cross_runner() { + None => CommandBuilder::new(btm_exe), + Some(runner) => { + let mut cmd = CommandBuilder::new(runner); + for (env, val) in RUNNER_ENV_VARS { + cmd.env(env, val); + } + cmd.arg(BTM_EXE_PATH); + + cmd + } + }; + + let args = if args.is_empty() { &DEFAULT_CFG } else { args }; + let mut prev = ""; + for arg in args.iter() { + if prev == "-C" { + // This is the config file; make sure we set it to absolute path! + cmd.arg(abs_path(arg)); + } else { + cmd.arg(arg); + } + + prev = arg; + } + + (pair.master, pair.slave.spawn_command(cmd).unwrap()) +} diff --git a/tests/integration/valid_config_tests.rs b/tests/integration/valid_config_tests.rs new file mode 100644 index 00000000..b152edb3 --- /dev/null +++ b/tests/integration/valid_config_tests.rs @@ -0,0 +1,69 @@ +//! Tests config files that have sometimes caused issues despite being valid. + +use std::{io::Read, thread, time::Duration}; + +use crate::util::spawn_btm_in_pty; + +fn reader_to_string(mut reader: Box) -> String { + let mut buf = String::default(); + reader.read_to_string(&mut buf).unwrap(); + + buf +} + +fn run_and_kill(args: &[&str]) { + let (master, mut handle) = spawn_btm_in_pty(args); + let reader = master.try_clone_reader().unwrap(); + let _ = master.take_writer().unwrap(); + + const TIMES_CHECKED: u64 = 6; // Check 6 times, once every 500ms, for 3 seconds total. + + for _ in 0..TIMES_CHECKED { + thread::sleep(Duration::from_millis(500)); + match handle.try_wait() { + Ok(Some(exit)) => { + println!("output: {}", reader_to_string(reader)); + panic!("program terminated unexpectedly (exit status: {exit:?})"); + } + Err(e) => { + println!("output: {}", reader_to_string(reader)); + panic!("error while trying to wait: {e}") + } + _ => {} + } + } + + handle.kill().unwrap(); +} + +#[test] +fn test_basic() { + run_and_kill(&[]); +} + +/// A test to ensure that a bad config will fail the `run_and_kill` function. +#[test] +#[should_panic] +fn test_bad_basic() { + run_and_kill(&["--this_does_not_exist"]); +} + +#[test] +fn test_empty() { + run_and_kill(&["-C", "./tests/valid_configs/empty_config.toml"]); +} + +#[test] +fn test_many_proc() { + run_and_kill(&["-C", "./tests/valid_configs/many_proc.toml"]); +} + +#[test] +fn test_all_proc() { + run_and_kill(&["-C", "./tests/valid_configs/all_proc.toml"]); +} + +#[test] +fn test_cpu_doughnut() { + run_and_kill(&["-C", "./tests/valid_configs/cpu_doughnut.toml"]); +} diff --git a/tests/util.rs b/tests/util.rs deleted file mode 100644 index b89a3027..00000000 --- a/tests/util.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{env, process::Command}; - -use hashbrown::HashMap; - -/// Returns a QEMU runner target given an architecture. -fn get_qemu_target(arch: &str) -> &str { - match arch { - "armv7" => "arm", - "i686" => "i386", - "powerpc" => "ppc", - "powerpc64le" => "ppc64le", - _ => arch, - } -} - -/// This is required since running binary tests via cross can cause be tricky! We need to basically "magically" grab -/// the correct runner in some cases, which can be done by inspecting env variables that should only show up while -/// using cross. -/// -/// Originally inspired by [ripgrep's test files](https://cs.github.com/BurntSushi/ripgrep/blob/9f0e88bcb14e02da1b88872435b17d74786640b5/tests/util.rs#L470), -/// but adapted to work more generally with the architectures supported by bottom after looking through cross' -/// [linux-runner](https://github.com/cross-rs/cross/blob/main/docker/linux-runner) file. -fn cross_runner() -> Option { - const TARGET_RUNNER: &str = "CARGO_TARGET_RUNNER"; - const CROSS_RUNNER: &str = "CROSS_RUNNER"; - - let env_mapping = env::vars_os() - .filter_map(|(k, v)| { - let (k, v) = (k.to_string_lossy(), v.to_string_lossy()); - - if k.starts_with("CARGO_TARGET_") && k.ends_with("_RUNNER") && !v.is_empty() { - Some((TARGET_RUNNER.to_string(), v.to_string())) - } else if k == CROSS_RUNNER && !v.is_empty() { - Some((k.to_string(), v.to_string())) - } else { - None - } - }) - .collect::>(); - - if let Some(cross_runner) = env_mapping.get(CROSS_RUNNER) { - if cross_runner == "qemu-user" { - env_mapping.get(TARGET_RUNNER).map(|target_runner| { - format!( - "qemu-{}", - get_qemu_target( - target_runner - .split_ascii_whitespace() - .collect::>() - .last() - .unwrap() - ) - ) - }) - } else { - None - } - } else { - env_mapping.get(TARGET_RUNNER).cloned() - } -} - -/// Returns the [`Command`] of a binary invocation of bottom, alongside -/// any required env variables. -pub fn btm_command() -> Command { - let btm_exe = env!("CARGO_BIN_EXE_btm"); - match cross_runner() { - None => Command::new(btm_exe), - Some(runner) => { - let mut cmd = Command::new(runner); - cmd.env("NO_COLOR", "1"); - cmd.arg(btm_exe); - cmd - } - } -} diff --git a/tests/valid_configs/all_proc.toml b/tests/valid_configs/all_proc.toml new file mode 100644 index 00000000..781e3836 --- /dev/null +++ b/tests/valid_configs/all_proc.toml @@ -0,0 +1,22 @@ +[[row]] +ratio = 30 +[[row.child]] +type = "proc" +[[row]] +ratio = 40 +[[row.child]] +ratio = 4 +type = "proc" +[[row.child]] +ratio = 3 +[[row.child.child]] +type = "proc" +[[row.child.child]] +type = "proc" +[[row]] +ratio = 30 +[[row.child]] +type = "proc" +[[row.child]] +type = "proc" +default = true diff --git a/tests/valid_configs/cpu_doughnut.toml b/tests/valid_configs/cpu_doughnut.toml new file mode 100644 index 00000000..c099078f --- /dev/null +++ b/tests/valid_configs/cpu_doughnut.toml @@ -0,0 +1,29 @@ +[[row]] +[[row.child]] +type = "cpu" +[[row.child]] +type = "cpu" +[[row.child]] +type = "cpu" + +[[row]] +[[row.child]] +[[row.child.child]] +type = "cpu" +[[row.child.child]] +type = "cpu" +[[row.child]] +type = "empty" +[[row.child]] +[[row.child.child]] +type = "cpu" +[[row.child.child]] +type = "cpu" + +[[row]] +[[row.child]] +type = "cpu" +[[row.child]] +type = "cpu" +[[row.child]] +type = "cpu" diff --git a/tests/empty_config.toml b/tests/valid_configs/empty_config.toml similarity index 100% rename from tests/empty_config.toml rename to tests/valid_configs/empty_config.toml diff --git a/tests/valid_configs/many_proc.toml b/tests/valid_configs/many_proc.toml new file mode 100644 index 00000000..440eb469 --- /dev/null +++ b/tests/valid_configs/many_proc.toml @@ -0,0 +1,22 @@ +[[row]] +ratio = 30 +[[row.child]] +type = "cpu" +[[row]] +ratio = 40 +[[row.child]] +ratio = 4 +type = "mem" +[[row.child]] +ratio = 3 +[[row.child.child]] +type = "proc" +[[row.child.child]] +type = "proc" +[[row]] +ratio = 30 +[[row.child]] +type = "net" +[[row.child]] +type = "proc" +default = true