Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ Cargo.lock

.vscode

Dockerfile.arm64
Dockerfile.arm64
.idea
8 changes: 6 additions & 2 deletions services/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ members = [
"desktop/client/dbus",
"desktop/server",
"store/server",
"store/client/dbus",
"store/client/dbus", "freedesktop/network-manager/freedesktop-network-manager-client", "freedesktop/upower/freedesktop-upower-client", "freedesktop/bluez/freedesktop-bluez-client", "freedesktop/pulseaudio/sound", "freedesktop/timedate/freedesktop-timedate-client",
]
default-members = ["system/server/dbus", "desktop/server"]

Expand Down Expand Up @@ -49,7 +49,7 @@ keywords = [
anyhow = "1"
tokio = { version = "1", features = ["full"] }
tracing = "0.1.40"
zbus = { version = "4.1.2", features = ["tokio"] }
zbus = { version = "5.5.0", features = ["tokio"] }
serde = { version = "1.0.164", features = ["derive"] }
serde_yaml = "0.9.21"
dotenv = "0.15.0"
Expand All @@ -68,3 +68,7 @@ futures = "0.3.30"
evdev = { version = "0.12.2", features = ["tokio"] }
futures-util = "0.3.30"
wayland-protocols-async = { git = "https://github.com/mecha-org/wayland-protocols-async.git" }
thiserror = { version = "2.0.12" }
async-trait = { version = "0.1.88" }
log = { version = "0.4.27" }
mockall = { version = "0.13.1" }
13 changes: 13 additions & 0 deletions services/freedesktop/bluez/freedesktop-bluez-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "freedesktop-bluez-client"
version = "0.1.0"
edition = "2024"

[dependencies]
zbus = { workspace = true }
tokio = { workspace = true }
anyhow = { workspace = true }
thiserror = { workspace = true }
async-trait = { workspace = true }
log = { workspace = true}
mockall = { workspace = true }
65 changes: 65 additions & 0 deletions services/freedesktop/bluez/freedesktop-bluez-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# freedesktop-bluez-client

A Rust library for interacting with Bluetooth via D-Bus and BlueZ on Linux. This crate provides an ergonomic, async API
for enabling/disabling Bluetooth, scanning for devices, connecting/disconnecting, and more.

## Features

- **Async API**: Built with Tokio for async/await support.
- **Bluetooth Management**: Power on/off, scan, connect, disconnect, and query devices.
- **Channel-based Requests**: Communicate with the Bluetooth handler using channels for flexible integration.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
freedesktop_bluez_client = "0.1.0" # Use the latest version
tokio = { version = "1", features = ["full"] }
anyhow = "1"
```

## Example

### Enable Bluetooth

```rust
use freedesktop_bluez_client::error::BluezError;
use freedesktop_bluez_client::handler::{BluezRequest, BluezClient};
use tokio::sync::{mpsc, oneshot};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (bt_tx, bt_rx) = mpsc::channel(10);
// Spawn the Bluetooth handler
let _handler = tokio::spawn(async move {
let mut bt_handler = BluezClient::new().await.unwrap();
let _ = bt_handler.run(bt_rx).await;
});

let (res_tx, res_rx) = oneshot::channel();

// Request to power on Bluetooth
bt_tx.try_send(BluezRequest::SetPoweredOn { reply_to: res_tx })
.expect("Failed to send Bluetooth request");

if let Ok(result) = res_rx.await {
match result {
Ok(()) => println!("Bluetooth powered on"),
Err(e) => println!("Error: {:?}", e),
}
}
Ok(())

```

## Error Handling

All operations return a `Result` with a custom `BluezError` enum, which can represent various error cases such as
D-Bus errors, BlueZ proxy errors, or generic failures.

## Contributing

Contributions are welcome! Please open issues or pull requests for bug fixes, improvements, or new features.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Basic example: PowerOn the Bluetooth device using freedesktop-bluez-client

use freedesktop_bluez_client::errors::BluezError;
use freedesktop_bluez_client::handler::{BluezRequest, BluezClient};
use tokio::sync::{mpsc, oneshot};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create a channel for sending Bluetooth requests
let (bt_tx, bt_rx) = mpsc::channel(10);

// Spawn the Bluetooth handler in a background task
let _handler = tokio::spawn(async move {
let mut bt_handler = BluezClient::new().await.unwrap();
// Run the handler event loop
let _ = bt_handler.run(bt_rx).await;
});
println!("handler spawned");
let (res_tx, res_rx) = oneshot::channel();
// Example: Enable Bluetooth
let request = BluezRequest::SetPoweredOn { reply_to: res_tx };
bt_tx
.try_send(request)
.expect("Failed to send Bluetooth request");
println!("Bluetooth enable request sent");

if let Ok(result) = res_rx.await {
match result {
Ok(_) => println!("bluetooth powered on"),
Err(e) => match e {
BluezError::Generic => {
println!("Generic error occurred");
}
BluezError::ProxyError(err) => {
println!("Proxy error occurred: {:?}", err);
}
BluezError::InitBusError(_) => {
println!("Failed to initialize system bus");
}
BluezError::CreateBluezProxyError(_) => {
println!("Failed to create Bluez proxy");
}
BluezError::CreateAdapterProxyError(_) => {
println!("Failed to create adapter proxy");
}
_ => {
println!("An unexpected error occurred: {:?}", e);
}
},
}
} else {
eprintln!("Did not receive a response for power on request");
}

// (Optional) gracefully shut down or send more requests...

// Wait for the handler to finish (in real code, you'd keep the handler running)
// handler.await?;

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Basic example: Scan available Bluetooth devices using freedesktop-bluez-client

use freedesktop_bluez_client::handler::{BluezClient, BluezRequest};
use tokio::sync::mpsc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create a channel for sending Bluetooth requests
let (bt_tx, bt_rx) = mpsc::channel(10);

// Spawn the Bluetooth handler in a background task
let _handler = tokio::spawn(async move {
let mut bt_handler = BluezClient::new().await.unwrap();
// Run the handler event loop
let _ = bt_handler.run(bt_rx).await;
});
println!("handler spawned");
let (res_tx, mut res_rx) = mpsc::channel(10);
// Example: Get available devices
let request = BluezRequest::GetAvailableDevices {
discovery_duration: 5000,
reply_to: res_tx,
};
bt_tx
.try_send(request)
.expect("Failed to send Bluetooth request");
println!("scan devices request sent");

let handler = tokio::spawn(async move {
while let Some(result) = res_rx.recv().await {
match result {
Ok(devices) => println!("Available devices: {:?}", devices),
Err(e) => eprintln!("Error getting available devices: {e}"),
}
}
});
// Await the result and log it
handler.await?;

// (Optional) gracefully shut down or send more requests...

// Wait for the handler to finish (in real code, you'd keep the handler running)
// handler.await?;

Ok(())
}
28 changes: 28 additions & 0 deletions services/freedesktop/bluez/freedesktop-bluez-client/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Bluez errors

use super::proxies::ProxyError;

/// An error that can occur while handling bluetooth operations.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum BluezError {
/// A generic, unspecified error.
#[error("generic error")]
Generic,

/// An error originating from the proxy layer.
#[error("proxy error: {0}")]
ProxyError(#[from] ProxyError),

/// Failure to create the system D-Bus connection.
#[error("failed to initialize system bus: {0}")]
InitBusError(String),

/// Failure to create the BlueZ proxy.
#[error("failed to create bluez proxy: {0}")]
CreateBluezProxyError(String),

/// Failure to create the adapter proxy.
#[error("failed to create adapter proxy: {0}")]
CreateAdapterProxyError(String),
}
Loading