From c266d2cf9b0355a185e7f479ea1e2c29d9ec0d25 Mon Sep 17 00:00:00 2001 From: Zackary Troop Date: Thu, 14 Mar 2024 02:36:57 -0400 Subject: [PATCH] Add partial support for viewing service data --- src/scan.rs | 1 + src/structs.rs | 3 +++ src/utils.rs | 16 ++++++++++++++ src/viewer.rs | 19 ++++++++++++++++- src/widgets/info_table.rs | 2 ++ src/widgets/inspect_overlay.rs | 39 ++++++++++++++++++++++++++++++++++ src/widgets/mod.rs | 1 + 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/widgets/inspect_overlay.rs diff --git a/src/scan.rs b/src/scan.rs index d18056f..7ca2ccf 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -47,6 +47,7 @@ pub async fn bluetooth_scan( properties.rssi, properties.manufacturer_data, properties.services, + properties.service_data, )); // Send a clone of the accumulated device information so far diff --git a/src/structs.rs b/src/structs.rs index 224fae1..6843b10 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -13,6 +13,7 @@ pub struct DeviceInfo { pub manufacturer_data: HashMap>, pub services: Vec, pub detected_at: String, + pub service_data: HashMap>, } impl DeviceInfo { @@ -25,6 +26,7 @@ impl DeviceInfo { rssi: Option, manufacturer_data: HashMap>, services: Vec, + service_data: HashMap>, ) -> Self { DeviceInfo { id, @@ -35,6 +37,7 @@ impl DeviceInfo { manufacturer_data, services, detected_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), + service_data } } } diff --git a/src/utils.rs b/src/utils.rs index 2466a16..d82d58c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use ratatui::layout::Rect; + use crate::company_codes::COMPANY_CODE; /// Extracts the manufacturer data from a `HashMap>` and returns a tuple with the company name and the manufacturer data as a string. @@ -26,3 +28,17 @@ pub fn extract_manufacturer_data(manufacturer_data: &HashMap>) -> ( None => ("n/a".to_string(), m), } } + +/// Returns a `Rect` with the provided percentage of the parent `Rect` and centered. +pub fn centered_rect(percent_x: u16, percent_y: u16, size: Rect) -> Rect { + let popup_size = Rect { + width: size.width * percent_x / 100, + height: size.height * percent_y / 100, + ..Rect::default() + }; + Rect { + x: (size.width - popup_size.width) / 2, + y: (size.height - popup_size.height) / 2, + ..popup_size + } +} \ No newline at end of file diff --git a/src/viewer.rs b/src/viewer.rs index 23b8939..846b652 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -1,6 +1,6 @@ use crossterm::event::{self, Event, KeyCode}; use ratatui::backend::Backend; -use ratatui::widgets::TableState; +use ratatui::widgets::{Clear, TableState}; use ratatui::{ layout::{Constraint, Direction, Layout}, Terminal, @@ -12,9 +12,11 @@ use std::time::Duration; use tokio::sync::mpsc; use crate::structs::DeviceInfo; +use crate::utils::centered_rect; use crate::widgets::detail_table::detail_table; use crate::widgets::device_table::device_table; use crate::widgets::info_table::info_table; +use crate::widgets::inspect_overlay::inspect_overlay; /// Displays the detected Bluetooth devices in a table and handles the user input. /// The user can navigate the table, pause the scanning, and quit the application. @@ -27,6 +29,7 @@ pub async fn viewer( let mut table_state = TableState::default(); table_state.select(Some(0)); let mut devices = Vec::::new(); + let mut inspect_view = false; loop { // Draw UI @@ -55,6 +58,17 @@ pub async fn viewer( // Draw the info table let info_table = info_table(pause_signal.load(Ordering::SeqCst)); f.render_widget(info_table, chunks[2]); + + let selected = table_state.selected(); + if inspect_view && selected.is_some() { + let device: &DeviceInfo = &devices[selected.unwrap()]; + if !device.service_data.is_empty() { + let inspect_overlay = inspect_overlay(device); + let area = centered_rect(60, 60, f.size()); + f.render_widget(Clear, area); + f.render_widget(inspect_overlay, area); + } + } })?; // Event handling @@ -66,6 +80,9 @@ pub async fn viewer( let current_state = pause_signal.load(Ordering::SeqCst); pause_signal.store(!current_state, Ordering::SeqCst); } + KeyCode::Enter => { + inspect_view = !inspect_view; + } KeyCode::Down => { let next = match table_state.selected() { Some(selected) => { diff --git a/src/widgets/info_table.rs b/src/widgets/info_table.rs index c6fb94d..04b1f94 100644 --- a/src/widgets/info_table.rs +++ b/src/widgets/info_table.rs @@ -9,6 +9,7 @@ pub fn info_table(signal: bool) -> Table<'static> { let info_rows = vec![Row::new(vec![ "[q → quit]", "[up/down → navigate]", + "[enter → inspection]", if signal { "[s → start scanning]" } else { @@ -22,6 +23,7 @@ pub fn info_table(signal: bool) -> Table<'static> { Constraint::Length(10), Constraint::Length(20), Constraint::Length(20), + Constraint::Length(20), ], ) .column_spacing(1); diff --git a/src/widgets/inspect_overlay.rs b/src/widgets/inspect_overlay.rs new file mode 100644 index 0000000..209416c --- /dev/null +++ b/src/widgets/inspect_overlay.rs @@ -0,0 +1,39 @@ +use ratatui::{ + layout::Constraint, + style::{Color, Modifier, Style}, + widgets::{Block, Borders, Row, Table}, +}; + +use crate::structs::DeviceInfo; + +/// Provides an overlay with the selected device's service data. +pub fn inspect_overlay(selected_device: &DeviceInfo) -> Table<'static> { + // Iterate through the selected device's service_data to create rows + let rows: Vec = selected_device + .service_data + .iter() + .map(|(uuid, data)| { + let data_str = data + .iter() + .map(|byte| format!("{:02x}", byte)) + .collect::>() + .join(" "); + // Create a row for each UUID and its corresponding data + Row::new(vec![uuid.to_string(), data_str]) + }) + .collect(); + + let table = Table::new( + rows, + [Constraint::Percentage(50), Constraint::Percentage(50)], + ) + .header(Row::new(vec!["UUID", "Data"]).style(Style::default().fg(Color::Yellow))) + .block( + Block::default() + .borders(Borders::ALL) + .title("Data Overview"), + ) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)); + + table +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 963db0f..6601809 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,4 @@ pub mod detail_table; pub mod device_table; pub mod info_table; +pub mod inspect_overlay; \ No newline at end of file