From 7f51f741d6cc7a5a7297b3b9e437f1f9f4f9cb6d Mon Sep 17 00:00:00 2001 From: pythops Date: Sun, 7 Jul 2024 22:51:40 +0200 Subject: [PATCH] feat: Add option to rename a paired device --- Readme.md | 3 +- Release.md | 1 + src/app.rs | 148 ++++++- src/bluetooth.rs | 30 +- src/config.rs | 8 + src/handler.rs | 1093 +++++++++++++++++++++++++--------------------- src/help.rs | 10 +- src/ui.rs | 7 +- 8 files changed, 763 insertions(+), 537 deletions(-) diff --git a/Readme.md b/Readme.md index ec7fe8b..01f2b5d 100644 --- a/Readme.md +++ b/Readme.md @@ -61,7 +61,7 @@ This will produce an executable file at `target/release/bluetui` that you can co `esc`: Dismiss the help pop-up. -`q` or `ctrl+c`: Quit the app. +`ctrl+c`: Quit the app. ### Adapters @@ -99,6 +99,7 @@ toggle_discovery = "d" unpair = "u" toggle_connect = " " toggle_trust = "t" +rename = "e" [new_device] pair = "p" diff --git a/Release.md b/Release.md index 8c019e9..8240fd4 100644 --- a/Release.md +++ b/Release.md @@ -2,6 +2,7 @@ ### Added +- Rename paired devices - Support light background ## v0.4 - 15/03/2024 diff --git a/src/app.rs b/src/app.rs index 15b54b3..a9d4447 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,15 +5,16 @@ use bluer::{ use futures::FutureExt; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, - style::{Color, Style, Stylize}, - text::{Span, Text}, + style::{Color, Modifier, Style, Stylize}, + text::{Line, Span, Text}, widgets::{ - Block, BorderType, Borders, Cell, Clear, Row, Scrollbar, ScrollbarOrientation, - ScrollbarState, Table, TableState, + Block, BorderType, Borders, Cell, Clear, Padding, Paragraph, Row, Scrollbar, + ScrollbarOrientation, ScrollbarState, Table, TableState, }, Frame, }; use std::sync::mpsc::channel; +use tui_input::Input; use crate::{ bluetooth::{request_confirmation, Controller}, @@ -39,6 +40,7 @@ pub enum FocusedBlock { NewDevices, Help, PassKeyConfirmation, + SetDeviceAliasBox, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -62,6 +64,7 @@ pub struct App { pub focused_block: FocusedBlock, pub pairing_confirmation: PairingConfirmation, pub color_mode: ColorMode, + pub new_alias: Input, } #[derive(Debug)] @@ -134,6 +137,118 @@ impl App { } } } + + pub fn render_set_alias(&mut self, frame: &mut Frame) { + let area = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(45), + Constraint::Length(6), + Constraint::Percentage(45), + ] + .as_ref(), + ) + .split(frame.size()); + + let area = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Length((frame.size().width - 80) / 2), + Constraint::Min(80), + Constraint::Length((frame.size().width - 80) / 2), + ] + .as_ref(), + ) + .split(area[1]); + + let area = area[1]; + + let (text_area, alias_area) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Length(1), + Constraint::Length(3), + Constraint::Length(1), + Constraint::Length(2), + ] + .as_ref(), + ) + .split(area); + + let area1 = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Length(1), + Constraint::Fill(1), + Constraint::Length(1), + ] + .as_ref(), + ) + .split(chunks[1]); + + let area2 = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(20), + Constraint::Fill(1), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(chunks[2]); + + (area1[1], area2[1]) + }; + + frame.render_widget(Clear, area); + frame.render_widget( + Block::new() + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .style(Style::default().green()) + .border_style(Style::default().fg(Color::Green)), + area, + ); + + if let Some(selected_controller) = self.controller_state.selected() { + let controller = &self.controllers[selected_controller]; + if let Some(index) = self.paired_devices_state.selected() { + let name = controller.paired_devices[index].alias.as_str(); + + let text = Line::from(vec![ + Span::from("Enter the new name for "), + Span::styled( + name, + Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC), + ), + ]); + + let msg = Paragraph::new(text) + .alignment(Alignment::Center) + .style(Style::default().fg(Color::White)) + .block(Block::new().padding(Padding::horizontal(2))); + + let alias = Paragraph::new(self.new_alias.value()) + .alignment(Alignment::Left) + .style(Style::default().fg(Color::White)) + .block( + Block::new() + .bg(Color::DarkGray) + .padding(Padding::horizontal(2)), + ); + + frame.render_widget(msg, text_area); + frame.render_widget(alias, alias_area); + } + } + } + pub fn render(&mut self, frame: &mut Frame) { if let Some(selected_controller_index) = self.controller_state.selected() { let selected_controller = &self.controllers[selected_controller_index]; @@ -293,7 +408,7 @@ impl App { ScrollbarState::new(self.controllers.len()).position(selected_controller_index); frame.render_stateful_widget( scrollbar, - controller_block.inner(&Margin { + controller_block.inner(Margin { vertical: 1, horizontal: 0, }), @@ -306,7 +421,13 @@ impl App { .iter() .map(|d| { Row::new(vec![ - d.alias.to_owned(), + { + if let Some(icon) = &d.icon { + format!("{} {}", icon, &d.alias) + } else { + d.alias.to_owned() + } + }, d.is_trusted.to_string(), d.is_connected.to_string(), { @@ -477,7 +598,7 @@ impl App { .position(self.paired_devices_state.selected().unwrap_or_default()); frame.render_stateful_widget( scrollbar, - paired_devices_block.inner(&Margin { + paired_devices_block.inner(Margin { vertical: 1, horizontal: 0, }), @@ -490,7 +611,15 @@ impl App { let rows: Vec = selected_controller .new_devices .iter() - .map(|d| Row::new(vec![d.addr.to_string(), d.alias.to_owned()])) + .map(|d| { + Row::new(vec![d.addr.to_string(), { + if let Some(icon) = &d.icon { + format!("{} {}", icon, &d.alias) + } else { + d.alias.to_owned() + } + }]) + }) .collect(); let rows_len = rows.len(); @@ -576,7 +705,7 @@ impl App { ScrollbarState::new(rows_len).position(state.selected().unwrap_or_default()); frame.render_stateful_widget( scrollbar, - new_devices_block.inner(&Margin { + new_devices_block.inner(Margin { vertical: 1, horizontal: 0, }), @@ -725,6 +854,7 @@ impl App { focused_block: FocusedBlock::Adapter, pairing_confirmation, color_mode, + new_alias: Input::default(), }) } diff --git a/src/bluetooth.rs b/src/bluetooth.rs index e395108..26bd04b 100644 --- a/src/bluetooth.rs +++ b/src/bluetooth.rs @@ -5,6 +5,9 @@ use bluer::{ agent::{ReqError, ReqResult, RequestConfirmation}, Adapter, Address, Session, }; + +use bluer::Device as BTDevice; + use tokio::sync::oneshot; use crate::app::AppResult; @@ -24,7 +27,9 @@ pub struct Controller { #[derive(Debug, Clone)] pub struct Device { + device: BTDevice, pub addr: Address, + pub icon: Option, pub alias: String, pub is_paired: bool, pub is_trusted: bool, @@ -32,8 +37,13 @@ pub struct Device { pub battery_percentage: Option, } -// https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html impl Device { + pub async fn set_alias(&self, alias: String) -> AppResult<()> { + self.device.set_alias(alias).await?; + Ok(()) + } + + // https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html pub fn get_icon(name: &str) -> Option { match name { "audio-card" => Some(String::from("󰓃")), @@ -101,25 +111,21 @@ impl Controller { let is_connected = device.is_connected().await?; let battery_percentage = device.battery_percentage().await?; - let device = Device { + let dev = Device { + device, addr, - alias: { - if let Some(icon) = icon { - format!("{} {}", icon, alias) - } else { - alias - } - }, + alias, + icon, is_paired, is_trusted, is_connected, battery_percentage, }; - if device.is_paired { - paired_devices.push(device); + if dev.is_paired { + paired_devices.push(dev); } else { - new_devices.push(device); + new_devices.push(dev); } } diff --git a/src/config.rs b/src/config.rs index 6c3a6d9..0a26c87 100644 --- a/src/config.rs +++ b/src/config.rs @@ -50,6 +50,9 @@ pub struct PairedDevice { #[serde(default = "default_toggle_device_trust")] pub toggle_trust: char, + + #[serde(default = "default_set_new_name")] + pub rename: char, } impl Default for PairedDevice { @@ -58,10 +61,15 @@ impl Default for PairedDevice { unpair: 'u', toggle_connect: ' ', toggle_trust: 't', + rename: 'e', } } } +fn default_set_new_name() -> char { + 'e' +} + #[derive(Deserialize, Debug)] pub struct NewDevice { #[serde(default = "default_pair_new_device")] diff --git a/src/handler.rs b/src/handler.rs index 104ef37..c5f0ab5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -10,284 +10,489 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use futures::StreamExt; use tokio::sync::mpsc::UnboundedSender; +use tui_input::backend::crossterm::EventHandler; + pub async fn handle_key_events( key_event: KeyEvent, app: &mut App, sender: UnboundedSender, config: Arc, ) -> AppResult<()> { - match key_event.code { - // Exit the app - KeyCode::Char('q') => { - app.quit(); - } - KeyCode::Char('c') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.quit(); - } - } - - // Show help - KeyCode::Char('?') => { - app.focused_block = FocusedBlock::Help; - } - - // Discard help popup - KeyCode::Esc => { - if app.focused_block == FocusedBlock::Help { - app.focused_block = FocusedBlock::Adapter; - } - } - - // Switch focus - KeyCode::Tab => match app.focused_block { - FocusedBlock::Adapter => { - app.focused_block = FocusedBlock::PairedDevices; - app.reset_devices_state(); - } - FocusedBlock::PairedDevices => { + match app.focused_block { + FocusedBlock::SetDeviceAliasBox => match key_event.code { + KeyCode::Enter => { if let Some(selected_controller) = app.controller_state.selected() { let controller = &app.controllers[selected_controller]; - if controller.new_devices.is_empty() { - app.focused_block = FocusedBlock::Adapter - } else { - app.focused_block = FocusedBlock::NewDevices + if let Some(index) = app.paired_devices_state.selected() { + let device = &controller.paired_devices[index]; + match device.set_alias(app.new_alias.value().to_string()).await { + Ok(_) => { + Notification::send( + "Set New Alias".to_string(), + NotificationLevel::Info, + sender, + )?; + } + Err(e) => { + Notification::send( + e.to_string(), + NotificationLevel::Error, + sender, + )?; + } + } + app.focused_block = FocusedBlock::PairedDevices; + app.new_alias.reset(); } } } - FocusedBlock::NewDevices => app.focused_block = FocusedBlock::Adapter, - _ => {} + + KeyCode::Esc => { + app.focused_block = FocusedBlock::PairedDevices; + app.new_alias.reset(); + } + _ => { + app.new_alias + .handle_event(&crossterm::event::Event::Key(key_event)); + } }, + _ => { + match key_event.code { + // Exit the app + KeyCode::Char('c') => { + if key_event.modifiers == KeyModifiers::CONTROL { + app.quit(); + } + } - // scroll down - KeyCode::Char('j') | KeyCode::Down => match app.focused_block { - FocusedBlock::Adapter => { - if !app.controllers.is_empty() { - let i = match app.controller_state.selected() { - Some(i) => { - if i < app.controllers.len() - 1 { - i + 1 - } else { - i - } - } - None => 0, - }; + // Show help + KeyCode::Char('?') => { + app.focused_block = FocusedBlock::Help; + } - app.reset_devices_state(); - app.controller_state.select(Some(i)); + // Discard help popup + KeyCode::Esc => { + if app.focused_block == FocusedBlock::Help { + app.focused_block = FocusedBlock::Adapter; + } } - } - FocusedBlock::PairedDevices => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &mut app.controllers[selected_controller]; - - if !controller.paired_devices.is_empty() { - let i = match app.paired_devices_state.selected() { - Some(i) => { - if i < controller.paired_devices.len() - 1 { - i + 1 - } else { - i - } + // Switch focus + KeyCode::Tab => match app.focused_block { + FocusedBlock::Adapter => { + app.focused_block = FocusedBlock::PairedDevices; + app.reset_devices_state(); + } + FocusedBlock::PairedDevices => { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &app.controllers[selected_controller]; + if controller.new_devices.is_empty() { + app.focused_block = FocusedBlock::Adapter + } else { + app.focused_block = FocusedBlock::NewDevices } - None => 0, - }; - - app.paired_devices_state.select(Some(i)); + } } - } - } + FocusedBlock::NewDevices => app.focused_block = FocusedBlock::Adapter, + _ => {} + }, - FocusedBlock::NewDevices => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &mut app.controllers[selected_controller]; - - if !controller.new_devices.is_empty() { - let i = match app.new_devices_state.selected() { - Some(i) => { - if i < controller.new_devices.len() - 1 { - i + 1 - } else { - i + // scroll down + KeyCode::Char('j') | KeyCode::Down => match app.focused_block { + FocusedBlock::Adapter => { + if !app.controllers.is_empty() { + let i = match app.controller_state.selected() { + Some(i) => { + if i < app.controllers.len() - 1 { + i + 1 + } else { + i + } } - } - None => 0, - }; + None => 0, + }; - app.new_devices_state.select(Some(i)); + app.reset_devices_state(); + app.controller_state.select(Some(i)); + } } - } - } - FocusedBlock::Help => { - app.help.scroll_down(); - } - _ => {} - }, + FocusedBlock::PairedDevices => { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &mut app.controllers[selected_controller]; + + if !controller.paired_devices.is_empty() { + let i = match app.paired_devices_state.selected() { + Some(i) => { + if i < controller.paired_devices.len() - 1 { + i + 1 + } else { + i + } + } + None => 0, + }; - // scroll up - KeyCode::Char('k') | KeyCode::Up => match app.focused_block { - FocusedBlock::Adapter => { - if !app.controllers.is_empty() { - let i = match app.controller_state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 + app.paired_devices_state.select(Some(i)); } } - None => 0, - }; + } - app.reset_devices_state(); - app.controller_state.select(Some(i)); - } - } + FocusedBlock::NewDevices => { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &mut app.controllers[selected_controller]; + + if !controller.new_devices.is_empty() { + let i = match app.new_devices_state.selected() { + Some(i) => { + if i < controller.new_devices.len() - 1 { + i + 1 + } else { + i + } + } + None => 0, + }; - FocusedBlock::PairedDevices => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &mut app.controllers[selected_controller]; - if !controller.paired_devices.is_empty() { - let i = match app.paired_devices_state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } + app.new_devices_state.select(Some(i)); } - None => 0, - }; - app.paired_devices_state.select(Some(i)); + } } - } - } - FocusedBlock::NewDevices => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &mut app.controllers[selected_controller]; - if !controller.new_devices.is_empty() { - let i = match app.new_devices_state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 + FocusedBlock::Help => { + app.help.scroll_down(); + } + _ => {} + }, + + // scroll up + KeyCode::Char('k') | KeyCode::Up => match app.focused_block { + FocusedBlock::Adapter => { + if !app.controllers.is_empty() { + let i = match app.controller_state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } } + None => 0, + }; + + app.reset_devices_state(); + app.controller_state.select(Some(i)); + } + } + + FocusedBlock::PairedDevices => { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &mut app.controllers[selected_controller]; + if !controller.paired_devices.is_empty() { + let i = match app.paired_devices_state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } + } + None => 0, + }; + app.paired_devices_state.select(Some(i)); } - None => 0, - }; - app.new_devices_state.select(Some(i)); + } } - } - } - FocusedBlock::Help => { - app.help.scroll_up(); - } - _ => {} - }, - // Start/Stop Scan - KeyCode::Char(c) if c == config.toggle_scanning => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &app.controllers[selected_controller]; - - if controller.is_scanning.load(Ordering::Relaxed) { - controller - .is_scanning - .store(false, std::sync::atomic::Ordering::Relaxed); - - Notification::send( - "Scanning stopped".to_string(), - NotificationLevel::Info, - sender, - )?; - - app.spinner.active = false; - } else { - controller - .is_scanning - .store(true, std::sync::atomic::Ordering::Relaxed); - app.spinner.active = true; - let adapter = controller.adapter.clone(); - let is_scanning = controller.is_scanning.clone(); - tokio::spawn(async move { - let _ = Notification::send( - "Scanning started".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - - match adapter.discover_devices().await { - Ok(mut discover) => { - while let Some(_evt) = discover.next().await { - if !is_scanning.load(Ordering::Relaxed) { - break; + FocusedBlock::NewDevices => { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &mut app.controllers[selected_controller]; + if !controller.new_devices.is_empty() { + let i = match app.new_devices_state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } } - } + None => 0, + }; + app.new_devices_state.select(Some(i)); } - Err(e) => { + } + } + FocusedBlock::Help => { + app.help.scroll_up(); + } + _ => {} + }, + + // Start/Stop Scan + KeyCode::Char(c) if c == config.toggle_scanning => { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &app.controllers[selected_controller]; + + if controller.is_scanning.load(Ordering::Relaxed) { + controller + .is_scanning + .store(false, std::sync::atomic::Ordering::Relaxed); + + Notification::send( + "Scanning stopped".to_string(), + NotificationLevel::Info, + sender, + )?; + + app.spinner.active = false; + } else { + controller + .is_scanning + .store(true, std::sync::atomic::Ordering::Relaxed); + app.spinner.active = true; + let adapter = controller.adapter.clone(); + let is_scanning = controller.is_scanning.clone(); + tokio::spawn(async move { let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, + "Scanning started".to_string(), + NotificationLevel::Info, sender.clone(), ); - } + + match adapter.discover_devices().await { + Ok(mut discover) => { + while let Some(_evt) = discover.next().await { + if !is_scanning.load(Ordering::Relaxed) { + break; + } + } + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + }); } - }); + } } - } - } - _ => { - match app.focused_block { - FocusedBlock::PairedDevices => { - match key_event.code { - // Unpair - KeyCode::Char(c) if c == config.paired_device.unpair => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &app.controllers[selected_controller]; - if let Some(index) = app.paired_devices_state.selected() { - let addr = controller.paired_devices[index].addr; - match controller.adapter.remove_device(addr).await { - Ok(_) => { - let _ = Notification::send( - "Device unpaired".to_string(), - NotificationLevel::Info, - sender.clone(), - ); + _ => { + match app.focused_block { + FocusedBlock::PairedDevices => { + match key_event.code { + // Unpair + KeyCode::Char(c) if c == config.paired_device.unpair => { + if let Some(selected_controller) = + app.controller_state.selected() + { + let controller = &app.controllers[selected_controller]; + if let Some(index) = app.paired_devices_state.selected() { + let addr = controller.paired_devices[index].addr; + match controller.adapter.remove_device(addr).await { + Ok(_) => { + let _ = Notification::send( + "Device unpaired".to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); + } + } + + // Connect / Disconnect + KeyCode::Char(c) if c == config.paired_device.toggle_connect => { + if let Some(selected_controller) = + app.controller_state.selected() + { + let controller = &app.controllers[selected_controller]; + if let Some(index) = app.paired_devices_state.selected() { + let addr = controller.paired_devices[index].addr; + match controller.adapter.device(addr) { + Ok(device) => { + tokio::spawn(async move { + match device.is_connected().await { + Ok(is_connected) => { + if is_connected { + match device.disconnect().await + { + Ok(_) => { + let _ = Notification::send( + "Device disconnected" + .to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + } else { + match device.connect().await { + Ok(_) => { + let _ = Notification::send( + "Device connected" + .to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + } + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + }); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } } } } + + // Trust / Untrust + KeyCode::Char(c) if c == config.paired_device.toggle_trust => { + if let Some(selected_controller) = + app.controller_state.selected() + { + let controller = &app.controllers[selected_controller]; + if let Some(index) = app.paired_devices_state.selected() { + let addr = controller.paired_devices[index].addr; + match controller.adapter.device(addr) { + Ok(device) => { + tokio::spawn(async move { + match device.is_trusted().await { + Ok(is_trusted) => { + if is_trusted { + match device + .set_trusted(false) + .await + { + Ok(_) => { + let _ = Notification::send( + "Device untrusted" + .to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + } else { + match device + .set_trusted(true) + .await + { + Ok(_) => { + let _ = Notification::send( + "Device trusted" + .to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + } + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + }); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + } + } + } + + KeyCode::Char(c) if c == config.paired_device.rename => { + app.focused_block = FocusedBlock::SetDeviceAliasBox; + } + + _ => {} } } - // Connect / Disconnect - KeyCode::Char(c) if c == config.paired_device.toggle_connect => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &app.controllers[selected_controller]; - if let Some(index) = app.paired_devices_state.selected() { - let addr = controller.paired_devices[index].addr; - match controller.adapter.device(addr) { - Ok(device) => { - tokio::spawn(async move { - match device.is_connected().await { - Ok(is_connected) => { - if is_connected { - match device.disconnect().await { + FocusedBlock::Adapter => { + match key_event.code { + // toggle pairing + KeyCode::Char(c) if c == config.adapter.toggle_pairing => { + if let Some(selected_controller) = + app.controller_state.selected() + { + let adapter = &app.controllers[selected_controller].adapter; + tokio::spawn({ + let adapter = adapter.clone(); + async move { + match adapter.is_pairable().await { + Ok(is_pairable) => { + if is_pairable { + match adapter.set_pairable(false).await + { Ok(_) => { let _ = Notification::send( - "Device disconnected" + "Adapter unpairable" .to_string(), NotificationLevel::Info, sender.clone(), @@ -302,10 +507,10 @@ pub async fn handle_key_events( } } } else { - match device.connect().await { + match adapter.set_pairable(true).await { Ok(_) => { let _ = Notification::send( - "Device connected" + "Adapter pairable" .to_string(), NotificationLevel::Info, sender.clone(), @@ -329,36 +534,27 @@ pub async fn handle_key_events( ); } } - }); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } + } + }); } } - } - } - // Trust / Untrust - KeyCode::Char(c) if c == config.paired_device.toggle_trust => { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &app.controllers[selected_controller]; - if let Some(index) = app.paired_devices_state.selected() { - let addr = controller.paired_devices[index].addr; - match controller.adapter.device(addr) { - Ok(device) => { - tokio::spawn(async move { - match device.is_trusted().await { - Ok(is_trusted) => { - if is_trusted { - match device.set_trusted(false).await { + // toggle power + KeyCode::Char(c) if c == config.adapter.toggle_power => { + if let Some(selected_controller) = + app.controller_state.selected() + { + let adapter = &app.controllers[selected_controller].adapter; + tokio::spawn({ + let adapter = adapter.clone(); + async move { + match adapter.is_powered().await { + Ok(is_powered) => { + if is_powered { + match adapter.set_powered(false).await { Ok(_) => { let _ = Notification::send( - "Device untrusted" + "Adapter powered off" .to_string(), NotificationLevel::Info, sender.clone(), @@ -373,16 +569,15 @@ pub async fn handle_key_events( } } } else { - match device.set_trusted(true).await { + match adapter.set_powered(true).await { Ok(_) => { let _ = Notification::send( - "Device trusted" + "Adapter powered on" .to_string(), NotificationLevel::Info, sender.clone(), ); } - Err(e) => { let _ = Notification::send( e.to_string(), @@ -401,188 +596,129 @@ pub async fn handle_key_events( ); } } - }); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } + } + }); } } - } - } - - _ => {} - } - } - FocusedBlock::Adapter => { - match key_event.code { - // toggle pairing - KeyCode::Char(c) if c == config.adapter.toggle_pairing => { - if let Some(selected_controller) = app.controller_state.selected() { - let adapter = &app.controllers[selected_controller].adapter; - tokio::spawn({ - let adapter = adapter.clone(); - async move { - match adapter.is_pairable().await { - Ok(is_pairable) => { - if is_pairable { - match adapter.set_pairable(false).await { - Ok(_) => { - let _ = Notification::send( - "Adapter unpairable".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); + // toggle discovery + KeyCode::Char(c) if c == config.adapter.toggle_discovery => { + if let Some(selected_controller) = + app.controller_state.selected() + { + let adapter = &app.controllers[selected_controller].adapter; + tokio::spawn({ + let adapter = adapter.clone(); + async move { + match adapter.is_discoverable().await { + Ok(is_discoverable) => { + if is_discoverable { + match adapter + .set_discoverable(false) + .await + { + Ok(_) => { + let _ = Notification::send( + "Adapter undiscoverable" + .to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } + } else { + match adapter + .set_discoverable(true) + .await + { + Ok(_) => { + let _ = Notification::send( + "Adapter discoverable" + .to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + } } } - } else { - match adapter.set_pairable(true).await { - Ok(_) => { - let _ = Notification::send( - "Adapter pairable".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); } } } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - } + }); } - }); - } - } + } - // toggle power - KeyCode::Char(c) if c == config.adapter.toggle_power => { - if let Some(selected_controller) = app.controller_state.selected() { - let adapter = &app.controllers[selected_controller].adapter; - tokio::spawn({ - let adapter = adapter.clone(); - async move { - match adapter.is_powered().await { - Ok(is_powered) => { - if is_powered { - match adapter.set_powered(false).await { - Ok(_) => { - let _ = Notification::send( - "Adapter powered off".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - } - } else { - match adapter.set_powered(true).await { - Ok(_) => { - let _ = Notification::send( - "Adapter powered on".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - } - } - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - } - } - }); + _ => {} } } - // toggle discovery - KeyCode::Char(c) if c == config.adapter.toggle_discovery => { - if let Some(selected_controller) = app.controller_state.selected() { - let adapter = &app.controllers[selected_controller].adapter; - tokio::spawn({ - let adapter = adapter.clone(); - async move { - match adapter.is_discoverable().await { - Ok(is_discoverable) => { - if is_discoverable { - match adapter.set_discoverable(false).await { - Ok(_) => { - let _ = Notification::send( - "Adapter undiscoverable" - .to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - } - } else { - match adapter.set_discoverable(true).await { - Ok(_) => { - let _ = Notification::send( - "Adapter discoverable".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); + FocusedBlock::NewDevices => { + // Pair new device + if KeyCode::Char(config.new_device.pair) == key_event.code { + if let Some(selected_controller) = app.controller_state.selected() { + let controller = &app.controllers[selected_controller]; + if let Some(index) = app.new_devices_state.selected() { + let addr = controller.new_devices[index].addr; + match controller.adapter.device(addr) { + Ok(device) => match device.alias().await { + Ok(device_name) => { + let _ = Notification::send( + format!( + "Start pairing with the device\n `{}` ", + device_name + ), + NotificationLevel::Info, + sender.clone(), + ); + + tokio::spawn(async move { + match device.pair().await { + Ok(_) => { + let _ = Notification::send( + "Device paired".to_string(), + NotificationLevel::Info, + sender.clone(), + ); + } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } } - } + }); } - } + Err(e) => { + let _ = Notification::send( + e.to_string(), + NotificationLevel::Error, + sender.clone(), + ); + } + }, Err(e) => { let _ = Notification::send( e.to_string(), @@ -592,101 +728,40 @@ pub async fn handle_key_events( } } } - }); + } } } - _ => {} - } - } - - FocusedBlock::NewDevices => { - // Pair new device - if KeyCode::Char(config.new_device.pair) == key_event.code { - if let Some(selected_controller) = app.controller_state.selected() { - let controller = &app.controllers[selected_controller]; - if let Some(index) = app.new_devices_state.selected() { - let addr = controller.new_devices[index].addr; - match controller.adapter.device(addr) { - Ok(device) => match device.alias().await { - Ok(device_name) => { - let _ = Notification::send( - format!( - "Start pairing with the device\n `{}` ", - device_name - ), - NotificationLevel::Info, - sender.clone(), - ); - - tokio::spawn(async move { - match device.pair().await { - Ok(_) => { - let _ = Notification::send( - "Device paired".to_string(), - NotificationLevel::Info, - sender.clone(), - ); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - } - }); - } - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } - }, - Err(e) => { - let _ = Notification::send( - e.to_string(), - NotificationLevel::Error, - sender.clone(), - ); - } + FocusedBlock::PassKeyConfirmation => match key_event.code { + KeyCode::Left | KeyCode::Char('h') => { + if !app.pairing_confirmation.confirmed { + app.pairing_confirmation.confirmed = true; + } + } + KeyCode::Right | KeyCode::Char('l') => { + if app.pairing_confirmation.confirmed { + app.pairing_confirmation.confirmed = false; } } - } - } - } - FocusedBlock::Help => {} + KeyCode::Enter => { + app.pairing_confirmation + .user_confirmation_sender + .send(app.pairing_confirmation.confirmed) + .await?; + app.pairing_confirmation + .display + .store(false, Ordering::Relaxed); + app.focused_block = FocusedBlock::PairedDevices; + app.pairing_confirmation.message = None; + } - FocusedBlock::PassKeyConfirmation => match key_event.code { - KeyCode::Left | KeyCode::Char('h') => { - if !app.pairing_confirmation.confirmed { - app.pairing_confirmation.confirmed = true; - } - } - KeyCode::Right | KeyCode::Char('l') => { - if app.pairing_confirmation.confirmed { - app.pairing_confirmation.confirmed = false; - } - } + _ => {} + }, - KeyCode::Enter => { - app.pairing_confirmation - .user_confirmation_sender - .send(app.pairing_confirmation.confirmed) - .await?; - app.pairing_confirmation - .display - .store(false, Ordering::Relaxed); - app.focused_block = FocusedBlock::PairedDevices; - app.pairing_confirmation.message = None; + _ => {} } - - _ => {} - }, + } } } } diff --git a/src/help.rs b/src/help.rs index 0904e2c..8dddc83 100644 --- a/src/help.rs +++ b/src/help.rs @@ -32,7 +32,7 @@ impl Help { Cell::from("## Global").style(Style::new().bold().fg(Color::Yellow)), "", ), - (Cell::from("Esc").bold(), "Dismiss help pop-up"), + (Cell::from("Esc").bold(), "Dismiss different pop-ups"), ( Cell::from("Tab").bold(), "Switch between different sections", @@ -44,7 +44,7 @@ impl Help { "Start/Stop scanning", ), (Cell::from("?").bold(), "Show help"), - (Cell::from("q or ctrl+c").bold(), "Quit"), + (Cell::from("ctrl+c").bold(), "Quit"), (Cell::from(""), ""), ( Cell::from("## Adapters").style(Style::new().bold().fg(Color::Yellow)), @@ -86,6 +86,10 @@ impl Help { Cell::from(config.paired_device.toggle_trust.to_string()).bold(), "Trust/Untrust the device", ), + ( + Cell::from(config.paired_device.rename.to_string()).bold(), + "Rename the device", + ), (Cell::from(""), ""), ( Cell::from("## New devices").style(Style::default().bold().fg(Color::Yellow)), @@ -167,7 +171,7 @@ impl Help { ScrollbarState::new(rows_len).position(self.state.selected().unwrap_or_default()); frame.render_stateful_widget( scrollbar, - block.inner(&Margin { + block.inner(Margin { vertical: 1, horizontal: 0, }), diff --git a/src/ui.rs b/src/ui.rs index 012b1ca..26d0662 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -6,9 +6,10 @@ pub fn render(app: &mut App, frame: &mut Frame) { // App app.render(frame); - // Help - if let FocusedBlock::Help = app.focused_block { - app.help.render(frame, app.color_mode); + match app.focused_block { + FocusedBlock::Help => app.help.render(frame, app.color_mode), + FocusedBlock::SetDeviceAliasBox => app.render_set_alias(frame), + _ => {} } // Notifications