Skip to content

Commit

Permalink
feat: Add option to rename a paired device
Browse files Browse the repository at this point in the history
  • Loading branch information
pythops committed Jul 7, 2024
1 parent 5e600b3 commit 7f51f74
Show file tree
Hide file tree
Showing 8 changed files with 763 additions and 537 deletions.
3 changes: 2 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -99,6 +99,7 @@ toggle_discovery = "d"
unpair = "u"
toggle_connect = " "
toggle_trust = "t"
rename = "e"

[new_device]
pair = "p"
Expand Down
1 change: 1 addition & 0 deletions Release.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Added

- Rename paired devices
- Support light background

## v0.4 - 15/03/2024
Expand Down
148 changes: 139 additions & 9 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -39,6 +40,7 @@ pub enum FocusedBlock {
NewDevices,
Help,
PassKeyConfirmation,
SetDeviceAliasBox,
}

#[derive(Debug, Clone, Copy, PartialEq)]
Expand All @@ -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)]
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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,
}),
Expand All @@ -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(),
{
Expand Down Expand Up @@ -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,
}),
Expand All @@ -490,7 +611,15 @@ impl App {
let rows: Vec<Row> = 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();

Expand Down Expand Up @@ -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,
}),
Expand Down Expand Up @@ -725,6 +854,7 @@ impl App {
focused_block: FocusedBlock::Adapter,
pairing_confirmation,
color_mode,
new_alias: Input::default(),
})
}

Expand Down
30 changes: 18 additions & 12 deletions src/bluetooth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,16 +27,23 @@ pub struct Controller {

#[derive(Debug, Clone)]
pub struct Device {
device: BTDevice,
pub addr: Address,
pub icon: Option<String>,
pub alias: String,
pub is_paired: bool,
pub is_trusted: bool,
pub is_connected: bool,
pub battery_percentage: Option<u8>,
}

// 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<String> {
match name {
"audio-card" => Some(String::from("󰓃")),
Expand Down Expand Up @@ -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);
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")]
Expand Down
Loading

0 comments on commit 7f51f74

Please sign in to comment.