Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
43bfbda
linux-rust: implement basic connections
kavishdevar Oct 20, 2025
cf2a242
linux-rust: add tray icon
kavishdevar Oct 21, 2025
c0ae061
linux-rust: remove percent character from tray
kavishdevar Oct 21, 2025
b0561e9
linux-rust: add conversational awareness
kavishdevar Oct 21, 2025
0f04290
linux-rust: fix conv-detect toggle in tray menu
kavishdevar Oct 21, 2025
ae5a701
linux-rust: fix battery parsing
kavishdevar Oct 21, 2025
7dd029f
linux-rust: add att
kavishdevar Oct 22, 2025
9da4c93
linux-rust: add tipi
kavishdevar Oct 22, 2025
221680f
linux-rust: remove ATT temporarily
kavishdevar Oct 22, 2025
e5c2419
linux-rust: optimize for size and add cli options
kavishdevar Oct 22, 2025
fec2263
linux-rust: catch errors
kavishdevar Oct 22, 2025
99940b9
linux-rust: add font
kavishdevar Oct 23, 2025
ccee820
linux-rust: store irk and enc keys
kavishdevar Oct 23, 2025
925c930
linux-rust: add ble skeleton
kavishdevar Oct 23, 2025
b474698
linux-rust: parse encrypted ble info
kavishdevar Oct 23, 2025
a007d9c
linux-rust: parse encrypted ble info
kavishdevar Oct 23, 2025
26cee5c
linux-rust: show battery info from LE in tray
kavishdevar Oct 23, 2025
3a0cc2e
linux-rust: remove sampling period from LE scanner
kavishdevar Oct 27, 2025
51b3d46
linux-rust: fix conv-detect and add le auto-connect
kavishdevar Oct 28, 2025
320964e
linux-rust: add dummy UI
kavishdevar Oct 28, 2025
17b5454
linux-rust: fix conv detect
kavishdevar Oct 28, 2025
99beeb5
linux-rust: add `--start-minimized`
kavishdevar Oct 28, 2025
c5a824c
linux-rust: remove unused features from iced
kavishdevar Oct 28, 2025
fa8bc11
linux-rust: parse and store device info
kavishdevar Oct 28, 2025
64470c4
linux-rust: show device info in UI
kavishdevar Nov 3, 2025
934df24
linux-rust: add bt communication to ui
kavishdevar Nov 5, 2025
a2cda68
linux-rust: add skeleton for other devices
kavishdevar Nov 6, 2025
bf6630d
linux-rust: implement bluetooth logic with ui
kavishdevar Nov 7, 2025
3853e8e
linux-rust: add listening mode picker for airpods
kavishdevar Nov 8, 2025
381b097
linux-rust: try enabling adaptive volume
kavishdevar Nov 8, 2025
23cf572
linux-rust: add anc control for nothing device
kavishdevar Nov 9, 2025
2049431
linux-rust: parse encrypted ble info
kavishdevar Oct 23, 2025
b7cd80e
linux-rust: cargo update
kavishdevar Nov 10, 2025
9f7f434
linux-rust: decrease icon size to 256x256
kavishdevar Nov 10, 2025
9d10eed
linux-rust: add desktop file
kavishdevar Nov 10, 2025
29c4225
linux-rust: add just file to build appimage
kavishdevar Nov 10, 2025
47f0213
linux-rust: add metainfo for flatpak
kavishdevar Nov 10, 2025
99e5b71
linux-rust: fix source name flatpak manifest
kavishdevar Nov 10, 2025
6585cf6
linux-rust: add session-bus to flatpak manifest for tray
kavishdevar Nov 10, 2025
0c9a2bd
linux-rust: rename binary
kavishdevar Nov 10, 2025
99a689a
linux-rust: update desktop entry to use rDNS icon name
kavishdevar Nov 10, 2025
b1f3856
linux-rust: update justfile for last two files
kavishdevar Nov 10, 2025
253ed65
linux-rust: add gitignore
kavishdevar Nov 10, 2025
a01e167
linux-rust: take version in justfile
kavishdevar Nov 10, 2025
093554d
linux-rust: add v0.1.0 to flatpak manifest
kavishdevar Nov 10, 2025
4737cbf
linux-rust: add battery to window and add option for text in tray
kavishdevar Nov 20, 2025
6f0323e
linux-rust: parse single battery of AirPods Max
kavishdevar Nov 22, 2025
e2d17b8
linux-rust: add nix flake (#371)
SophiaH67 Dec 11, 2025
376c542
feat(nix): add comprehensive Nix flake for linux-rust
doprz Dec 13, 2025
6ded8ff
feat(nix): add comprehensive Nix flake for linux-rust
doprz Dec 13, 2025
902b12a
fix(clippy): fix cargo clippy warnings
doprz Dec 13, 2025
c852b72
fix(linux-rust): format and fix syntax error
doprz Dec 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
linux-rust: add skeleton for other devices
  • Loading branch information
kavishdevar committed Nov 10, 2025
commit a2cda688d42e27a10e1132e1d57c1cdace5dc692
68 changes: 65 additions & 3 deletions linux-rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion linux-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ uuid = "1.18.1"
log = "0.4.28"
dbus = "0.9.9"
hex = "0.4.3"
iced = {version = "0.13.1", features = ["tokio"]}
iced = { version = "0.13.1", features = ["tokio", "image"] }
libpulse-binding = "2.30.1"
ksni = "0.3.1"
image = "0.25.8"
Expand Down
Binary file added linux-rust/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 28 additions & 39 deletions linux-rust/src/bluetooth/aacp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use tokio::time::{sleep, Instant};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json;
use crate::devices::airpods::AirPodsInformation;
use crate::devices::enums::{DeviceData, DeviceInformation, DeviceType};
use crate::utils::get_devices_path;

const PSM: u16 = 0x1001;
Expand Down Expand Up @@ -280,45 +282,11 @@ pub enum AACPEvent {
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeviceType {
AirPods,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LEData {
pub struct AirPodsLEKeys {
pub irk: String,
pub enc_key: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AirPodsInformation {
pub name: String,
pub model_number: String,
pub manufacturer: String,
pub serial_number: String,
pub version1: String,
pub version2: String,
pub hardware_revision: String,
pub updater_identifier: String,
pub left_serial_number: String,
pub right_serial_number: String,
pub version3: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", content = "data")]
pub enum DeviceInformation {
AirPods(AirPodsInformation),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceData {
pub name: String,
pub type_: DeviceType,
pub le: LEData,
pub information: Option<DeviceInformation>,
}

pub struct AACPManagerState {
pub sender: Option<mpsc::Sender<Vec<u8>>>,
pub control_command_status_list: Vec<ControlCommandStatus>,
Expand Down Expand Up @@ -647,7 +615,7 @@ impl AACPManager {
strings.push(s.to_string());
}
}
strings.remove(0); // Remove the first empty string as per comment
strings.remove(0);
let info = AirPodsInformation {
name: strings.get(0).cloned().unwrap_or_default(),
model_number: strings.get(1).cloned().unwrap_or_default(),
Expand All @@ -660,6 +628,10 @@ impl AACPManager {
left_serial_number: strings.get(8).cloned().unwrap_or_default(),
right_serial_number: strings.get(9).cloned().unwrap_or_default(),
version3: strings.get(10).cloned().unwrap_or_default(),
le_keys: AirPodsLEKeys {
irk: "".to_string(),
enc_key: "".to_string(),
},
};
let mut state = self.state.lock().await;
if let Some(mac) = state.airpods_mac {
Expand Down Expand Up @@ -715,12 +687,29 @@ impl AACPManager {
let device_data = state.devices.entry(mac_str.clone()).or_insert(DeviceData {
name: mac_str.clone(),
type_: DeviceType::AirPods,
le: LEData { irk: "".to_string(), enc_key: "".to_string() },
information: None,
});
match kt {
ProximityKeyType::Irk => device_data.le.irk = hex::encode(key_data),
ProximityKeyType::EncKey => device_data.le.enc_key = hex::encode(key_data),
ProximityKeyType::Irk => {
match device_data.information.as_mut() {
Some(DeviceInformation::AirPods(info)) => {
info.le_keys.irk = hex::encode(key_data);
}
_ => {
error!("Device information is not AirPods for adding LE IRK.");
}
}
}
ProximityKeyType::EncKey => {
match device_data.information.as_mut() {
Some(DeviceInformation::AirPods(info)) => {
info.le_keys.enc_key = hex::encode(key_data);
}
_ => {
error!("Device information is not AirPods for adding LE encryption key.");
}
}
}
}
}
}
Expand Down
55 changes: 34 additions & 21 deletions linux-rust/src/bluetooth/att.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,62 +16,67 @@ const OPCODE_READ_REQUEST: u8 = 0x0A;
const OPCODE_WRITE_REQUEST: u8 = 0x12;
const OPCODE_HANDLE_VALUE_NTF: u8 = 0x1B;
const OPCODE_WRITE_RESPONSE: u8 = 0x13;
const RESPONSE_TIMEOUT: u64 = 5000;

#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ATTHandles {
Transparency = 0x18,
LoudSoundReduction = 0x1B,
HearingAid = 0x2A,
AirPodsTransparency = 0x18,
AirPodsLoudSoundReduction = 0x1B,
AirPodsHearingAid = 0x2A,
NothingEverything = 0x8002,
NothingEverythingRead = 0x8005 // for some reason, and not the same as the write handle
}

#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ATTCCCDHandles {
Transparency = ATTHandles::Transparency as u16 + 1,
LoudSoundReduction = ATTHandles::LoudSoundReduction as u16 + 1,
HearingAid = ATTHandles::HearingAid as u16 + 1,
Transparency = ATTHandles::AirPodsTransparency as u16 + 1,
LoudSoundReduction = ATTHandles::AirPodsLoudSoundReduction as u16 + 1,
HearingAid = ATTHandles::AirPodsHearingAid as u16 + 1,
}

impl From<ATTHandles> for ATTCCCDHandles {
fn from(handle: ATTHandles) -> Self {
match handle {
ATTHandles::Transparency => ATTCCCDHandles::Transparency,
ATTHandles::LoudSoundReduction => ATTCCCDHandles::LoudSoundReduction,
ATTHandles::HearingAid => ATTCCCDHandles::HearingAid,
ATTHandles::AirPodsTransparency => ATTCCCDHandles::Transparency,
ATTHandles::AirPodsLoudSoundReduction => ATTCCCDHandles::LoudSoundReduction,
ATTHandles::AirPodsHearingAid => ATTCCCDHandles::HearingAid,
ATTHandles::NothingEverything => panic!("No CCCD for NothingEverything handle"), // we don't request it
ATTHandles::NothingEverythingRead => panic!("No CCD for NothingEverythingRead handle") // it sends notifications without CCCD
}
}
}

struct ATTManagerState {
sender: Option<mpsc::Sender<Vec<u8>>>,
listeners: HashMap<u16, Vec<mpsc::UnboundedSender<Vec<u8>>>>,
responses: mpsc::UnboundedReceiver<Vec<u8>>,
response_tx: mpsc::UnboundedSender<Vec<u8>>,
}

impl ATTManagerState {
fn new() -> Self {
let (tx, rx) = mpsc::unbounded_channel();
ATTManagerState {
sender: None,
listeners: HashMap::new(),
responses: rx,
response_tx: tx,
listeners: HashMap::new()
}
}
}

#[derive(Clone)]
pub struct ATTManager {
state: Arc<Mutex<ATTManagerState>>,
response_rx: Arc<Mutex<mpsc::UnboundedReceiver<Vec<u8>>>>,
response_tx: mpsc::UnboundedSender<Vec<u8>>,
tasks: Arc<Mutex<JoinSet<()>>>,
}

impl ATTManager {
pub fn new() -> Self {
let (tx, rx) = mpsc::unbounded_channel();
ATTManager {
state: Arc::new(Mutex::new(ATTManagerState::new())),
response_rx: Arc::new(Mutex::new(rx)),
response_tx: tx,
tasks: Arc::new(Mutex::new(JoinSet::new())),
}
}
Expand Down Expand Up @@ -184,11 +189,18 @@ impl ATTManager {
}

async fn read_response(&self) -> Result<Vec<u8>> {
let mut state = self.state.lock().await;
match tokio::time::timeout(Duration::from_millis(2000), state.responses.recv()).await {
debug!("Waiting for response...");
let mut rx = self.response_rx.lock().await;
match tokio::time::timeout(Duration::from_millis(RESPONSE_TIMEOUT), rx.recv()).await {
Ok(Some(resp)) => Ok(resp),
Ok(None) => Err(Error::from(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Response channel closed"))),
Err(_) => Err(Error::from(std::io::Error::new(std::io::ErrorKind::TimedOut, "Response timeout"))),
Ok(None) => Err(Error::from(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"Response channel closed"
))),
Err(_) => Err(Error::from(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"Response timeout"
))),
}
}
}
Expand Down Expand Up @@ -217,10 +229,11 @@ async fn recv_thread(manager: ATTManager, sp: Arc<SeqPacket>) {
let _ = listener.send(value.clone());
}
}
} else if data[0] == OPCODE_WRITE_RESPONSE {
let _ = manager.response_tx.send(vec![]);
} else {
// Response
let state = manager.state.lock().await;
let _ = state.response_tx.send(data[1..].to_vec());
let _ = manager.response_tx.send(data[1..].to_vec());
}
}
Err(e) => {
Expand Down
Loading