From 7d70dfd043e3560fa18ae7fcaefad253711ebab4 Mon Sep 17 00:00:00 2001 From: pythops Date: Sat, 24 Feb 2024 17:28:07 +0100 Subject: [PATCH] feat: handle pairing confirmation request --- Cargo.lock | 231 +++++++++++++++++++++++++++++++++++------------ Cargo.toml | 3 +- Release.md | 6 ++ src/app.rs | 200 ++++++++++++++++++++++++++++++++++++++-- src/bluetooth.rs | 39 +++++++- src/handler.rs | 32 ++++++- src/ui.rs | 31 +------ 7 files changed, 443 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2d7f1f..bc964ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ "cfg-if", "once_cell", @@ -35,6 +35,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "async-channel" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +dependencies = [ + "concurrent-queue", + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -99,8 +112,9 @@ dependencies = [ [[package]] name = "bluetui" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "async-channel", "bluer", "crossterm", "futures", @@ -131,12 +145,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" [[package]] name = "cfg-if" @@ -157,6 +168,21 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crossterm" version = "0.27.0" @@ -202,15 +228,15 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "synstructure", ] [[package]] name = "darling" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -218,27 +244,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "darling_macro" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -282,7 +308,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -291,6 +317,27 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "event-listener" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fnv" version = "1.0.7" @@ -353,7 +400,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -421,9 +468,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "hex" @@ -497,9 +544,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ "hashbrown", ] @@ -534,7 +581,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -556,7 +603,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -593,6 +640,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -613,7 +666,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -639,7 +692,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -656,9 +709,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "proc-macro2" @@ -721,9 +774,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "scopeguard" @@ -733,29 +786,29 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -809,12 +862,12 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -858,7 +911,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -874,9 +927,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -891,7 +944,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -910,7 +963,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -921,7 +974,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -1003,7 +1056,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.3", ] [[package]] @@ -1012,13 +1074,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +dependencies = [ + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -1027,42 +1104,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + [[package]] name = "zerocopy" version = "0.7.32" @@ -1080,5 +1199,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] diff --git a/Cargo.toml b/Cargo.toml index fc3191c..9a49457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bluetui" -version = "0.1.0" +version = "0.2.0" authors = ["pythops "] license = "GPL-3.0" edition = "2021" @@ -10,6 +10,7 @@ homepage = "https://github.com/pythops/bluetui" repository = "https://github.com/pythops/bluetui" [dependencies] +async-channel = "2.2.0" bluer = { version = "0.17", features = ["full"] } crossterm = { version = "0.27", features = ["event-stream"] } futures = "0.3" diff --git a/Release.md b/Release.md index ff14884..24b3cbc 100644 --- a/Release.md +++ b/Release.md @@ -1,3 +1,9 @@ +## v0.2 - TBA + +### Added + +- Handle pairing confirmation request + ## v0.1 - 22/02/2024 First release 🎉 diff --git a/src/app.rs b/src/app.rs index 5690d8b..77566c9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,12 +1,32 @@ +use bluer::{ + agent::{Agent, AgentHandle}, + Session, +}; +use futures::FutureExt; use ratatui::{ - layout::{Constraint, Direction, Layout}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Color, Style, Stylize}, - widgets::{Block, BorderType, Borders, Row, Table, TableState}, + text::{Span, Text}, + widgets::{Block, BorderType, Borders, Clear, Row, Table, TableState}, Frame, }; +use std::sync::mpsc::channel; -use crate::{bluetooth::Controller, help::Help, notification::Notification, spinner::Spinner}; -use std::{error, sync::atomic::Ordering}; +use crate::{ + bluetooth::{request_confirmation, Controller}, + help::Help, + notification::Notification, + spinner::Spinner, +}; +use std::{ + error, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use async_channel; pub type AppResult = std::result::Result>; @@ -16,11 +36,14 @@ pub enum FocusedBlock { PairedDevices, NewDevices, Help, + PassKeyConfirmation, } #[derive(Debug)] pub struct App { pub running: bool, + pub session: Arc, + pub agent: AgentHandle, pub help: Help, pub spinner: Spinner, pub notifications: Vec, @@ -29,8 +52,62 @@ pub struct App { pub paired_devices_state: TableState, pub new_devices_state: TableState, pub focused_block: FocusedBlock, + pub pairing_confirmation: PairingConfirmation, } +#[derive(Debug)] +pub struct PairingConfirmation { + pub confirmed: bool, + pub display: Arc, + pub message: Option, + pub user_confirmation_sender: async_channel::Sender, + pub user_confirmation_receiver: async_channel::Receiver, + pub confirmation_message_sender: std::sync::mpsc::Sender, + pub confirmation_message_receiver: std::sync::mpsc::Receiver, +} + +impl Default for PairingConfirmation { + fn default() -> Self { + let (user_confirmation_sender, user_confirmation_receiver) = async_channel::unbounded(); + + let (confirmation_message_sender, confirmation_message_receiver) = channel::(); + Self { + confirmed: true, + display: Arc::new(AtomicBool::new(false)), + message: None, + user_confirmation_sender, + user_confirmation_receiver, + confirmation_message_sender, + confirmation_message_receiver, + } + } +} + +pub fn popup(r: Rect) -> Rect { + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(45), + Constraint::Length(5), + Constraint::Percentage(45), + ] + .as_ref(), + ) + .split(r); + + Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Length((r.width - 80) / 2), + Constraint::Min(80), + Constraint::Length((r.width - 80) / 2), + ] + .as_ref(), + ) + .split(popup_layout[1])[1] +} impl App { pub fn reset_devices_state(&mut self) { if let Some(selected_controller) = self.controller_state.selected() { @@ -48,7 +125,7 @@ impl App { } } } - pub fn render(&self, frame: &mut Frame) { + 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]; // Layout @@ -255,10 +332,116 @@ impl App { frame.render_stateful_widget(new_devices_table, new_devices_block, &mut state); } + + if self.pairing_confirmation.display.load(Ordering::Relaxed) { + self.focused_block = FocusedBlock::PassKeyConfirmation; + if self.pairing_confirmation.message.is_none() { + let msg = self + .pairing_confirmation + .confirmation_message_receiver + .recv() + .unwrap(); + self.pairing_confirmation.message = Some(msg); + } + + let popup_area = popup(frame.size()); + + let (text_area, choices_area) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Length(1), + Constraint::Length(1), + Constraint::Length(1), + Constraint::Length(1), + Constraint::Length(1), + ] + .as_ref(), + ) + .split(popup_area); + + (chunks[1], chunks[3]) + }; + + let (yes_area, no_area) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(30), + Constraint::Length(5), + Constraint::Min(1), + Constraint::Length(5), + Constraint::Percentage(30), + ] + .as_ref(), + ) + .split(choices_area); + + (chunks[1], chunks[3]) + }; + + let text = Text::from( + self.pairing_confirmation + .message + .clone() + .unwrap_or_default(), + ); + let (yes, no) = { + if self.pairing_confirmation.confirmed { + let no = Span::from("[No]").style(Style::default()); + let yes = Span::from("[Yes]").style(Style::default().bg(Color::DarkGray)); + (yes, no) + } else { + let no = Span::from("[No]").style(Style::default().bg(Color::DarkGray)); + let yes = Span::from("[Yes]").style(Style::default()); + (yes, no) + } + }; + + frame.render_widget(Clear, popup_area); + + frame.render_widget( + Block::new() + .borders(Borders::ALL) + .border_type(BorderType::Thick), + popup_area, + ); + frame.render_widget(text.alignment(Alignment::Center), text_area); + frame.render_widget(yes, yes_area); + frame.render_widget(no, no_area); + } } } pub async fn new() -> AppResult { - let controllers: Vec = Controller::get_all().await?; + let session = Arc::new(bluer::Session::new().await?); + + // Pairing confirmation + let pairing_confirmation = PairingConfirmation::default(); + + let user_confirmation_receiver = pairing_confirmation.user_confirmation_receiver.clone(); + + let confirmation_message_sender = pairing_confirmation.confirmation_message_sender.clone(); + + let confirmation_display = pairing_confirmation.display.clone(); + + let agent = Agent { + request_default: false, + request_confirmation: Some(Box::new(move |req| { + request_confirmation( + req, + confirmation_display.clone(), + user_confirmation_receiver.clone(), + confirmation_message_sender.clone(), + ) + .boxed() + })), + ..Default::default() + }; + + let handle = session.register_agent(agent).await?; + let controllers: Vec = Controller::get_all(session.clone()).await?; let mut controller_state = TableState::default(); if controllers.is_empty() { @@ -269,6 +452,8 @@ impl App { Ok(Self { running: true, + session, + agent: handle, help: Help::new(), spinner: Spinner::default(), notifications: Vec::new(), @@ -277,6 +462,7 @@ impl App { paired_devices_state: TableState::default(), new_devices_state: TableState::default(), focused_block: FocusedBlock::Adapter, + pairing_confirmation, }) } @@ -292,7 +478,7 @@ impl App { } pub async fn refresh(&mut self) -> AppResult<()> { - let refreshed_controllers = Controller::get_all().await?; + let refreshed_controllers = Controller::get_all(self.session.clone()).await?; let names = { let mut names: Vec = Vec::new(); diff --git a/src/bluetooth.rs b/src/bluetooth.rs index 2a7d157..62ee208 100644 --- a/src/bluetooth.rs +++ b/src/bluetooth.rs @@ -1,6 +1,10 @@ -use std::sync::{atomic::AtomicBool, Arc}; +use std::sync::{atomic::AtomicBool, mpsc::Sender, Arc}; -use bluer::{Adapter, Address}; +use async_channel::Receiver; +use bluer::{ + agent::{ReqError, ReqResult, RequestConfirmation}, + Adapter, Address, Session, +}; use crate::app::AppResult; @@ -45,10 +49,10 @@ impl Device { } impl Controller { - pub async fn get_all() -> AppResult> { + pub async fn get_all(session: Arc) -> AppResult> { let mut controllers: Vec = Vec::new(); - let session = bluer::Session::new().await?; + // let session = bluer::Session::new().await?; let adapter_names = session.adapter_names().await?; for adapter_name in adapter_names { if let Ok(adapter) = session.adapter(&adapter_name) { @@ -113,3 +117,30 @@ impl Controller { Ok((paired_devices, new_devices)) } } + +pub async fn request_confirmation( + req: RequestConfirmation, + display_confirmation_popup: Arc, + rx: Receiver, + sender: Sender, +) -> ReqResult<()> { + display_confirmation_popup.store(true, std::sync::atomic::Ordering::Relaxed); + + sender + .send(format!( + "Is passkey \"{:06}\" correct for device {} on {}?", + req.passkey, &req.device, &req.adapter + )) + .unwrap(); + match rx.recv().await { + Ok(v) => { + // false: reject the confirmation + if !v { + return Err(ReqError::Rejected); + } + } + Err(_) => return Err(ReqError::Rejected), + } + + Ok(()) +} diff --git a/src/handler.rs b/src/handler.rs index f6eff53..bc97b01 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -53,7 +53,7 @@ pub async fn handle_key_events( } } FocusedBlock::NewDevices => app.focused_block = FocusedBlock::Adapter, - FocusedBlock::Help => {} + _ => {} }, // scroll down @@ -121,6 +121,7 @@ pub async fn handle_key_events( FocusedBlock::Help => { app.help.scroll_down(); } + _ => {} }, // scroll up @@ -183,6 +184,7 @@ pub async fn handle_key_events( FocusedBlock::Help => { app.help.scroll_up(); } + _ => {} }, // Start/Stop Scan @@ -268,6 +270,7 @@ pub async fn handle_key_events( } // Connect / Disconnect + // TODO: move to a task cause it is blocking KeyCode::Char(' ') => { if let Some(selected_controller) = app.controller_state.selected() { let controller = &app.controllers[selected_controller]; @@ -516,6 +519,33 @@ pub async fn handle_key_events( } FocusedBlock::Help => {} + + 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/ui.rs b/src/ui.rs index d738778..c2c4ff1 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,36 +1,7 @@ -use ratatui::{ - layout::{Constraint, Direction, Layout, Rect}, - Frame, -}; +use ratatui::Frame; use crate::app::{App, FocusedBlock}; -pub fn notification_rect(offset: u16, notification_height: u16, r: Rect) -> Rect { - let popup_layout = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Length(1 + notification_height * offset), - Constraint::Length(notification_height), - Constraint::Min(1), - ] - .as_ref(), - ) - .split(r); - - Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Min(1), - Constraint::Length(30), - Constraint::Length(2), - ] - .as_ref(), - ) - .split(popup_layout[1])[1] -} - pub fn render(app: &mut App, frame: &mut Frame) { // App app.render(frame);