mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-29 17:44:36 +00:00
linux-rust(feat): add stem press track control and headless mode support (#469)
* feat: add stem press track control and headless mode support - Parse STEM_PRESS packets and emit AACPEvent::StemPress with press type and bud side - Enable double/triple tap detection on init via StemConfig control command (0x06) - Double press → next track, triple press → previous track via MPRIS D-Bus - Add next_track() and previous_track() to MediaController - Add --no-tray flag for headless operation without a GUI - Replace unwrap() on ui_tx.send() calls with graceful warn! logging (vibecoded) * Update main.rs * feat: make stem press track control optional with GUI toggle Add a --no-stem-control CLI flag and a toggle in the Settings tab for environments that handle AirPods AVRCP commands natively (e.g. via BlueZ/PipeWire). The feature remains enabled by default. - Load stem_control from app settings JSON on startup; --no-stem-control overrides it to false regardless of the saved value - Share an Arc<AtomicBool> between the async backend and the GUI thread; AirPodsDevice holds the Arc directly so the event loop reads the live value on every stem press — toggle takes effect immediately without reconnecting - Persist stem_control to settings JSON alongside theme and tray_text_mode - Add a "Controls" section to the Settings tab with a toggler labelled "Stem press track control", with a subtitle explaining the AVRCP conflict scenario - Fix StemConfig bitmask comment to clarify it uses a separate numbering scheme from the StemPressType event enum values (0x05–0x08)
This commit is contained in:
@@ -8,6 +8,7 @@ use ksni::Handle;
|
||||
use log::{debug, error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
@@ -24,6 +25,7 @@ impl AirPodsDevice {
|
||||
mac_address: Address,
|
||||
tray_handle: Option<Handle<MyTray>>,
|
||||
ui_tx: tokio::sync::mpsc::UnboundedSender<BluetoothUIMessage>,
|
||||
stem_control: Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
info!("Creating new AirPodsDevice for {}", mac_address);
|
||||
let mut aacp_manager = AACPManager::new();
|
||||
@@ -80,6 +82,20 @@ impl AirPodsDevice {
|
||||
error!("Failed to request proximity keys: {}", e);
|
||||
}
|
||||
|
||||
if stem_control.load(Ordering::Relaxed) {
|
||||
// Enable stem press detection (double and triple tap)
|
||||
// StemConfig bitmask for the control command: single=0x01, double=0x02, triple=0x04, long=0x08
|
||||
// We want double and triple: 0x02 | 0x04 = 0x06
|
||||
// Note: these bitmask values differ from the StemPressType event enum values (0x05–0x08)
|
||||
info!("Enabling stem press detection for double and triple tap");
|
||||
if let Err(e) = aacp_manager
|
||||
.send_control_command(ControlCommandIdentifiers::StemConfig, &[0x06])
|
||||
.await
|
||||
{
|
||||
error!("Failed to enable stem press detection: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let session = bluer::Session::new()
|
||||
.await
|
||||
.expect("Failed to get bluer session");
|
||||
@@ -206,6 +222,7 @@ impl AirPodsDevice {
|
||||
let local_mac_events = local_mac.clone();
|
||||
let ui_tx_clone = ui_tx.clone();
|
||||
let command_tx_clone = command_tx.clone();
|
||||
let stem_control_clone = stem_control.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let event_clone = event.clone();
|
||||
@@ -325,6 +342,31 @@ impl AirPodsDevice {
|
||||
controller.pause_all_media().await;
|
||||
controller.deactivate_a2dp_profile().await;
|
||||
}
|
||||
AACPEvent::StemPress(press_type, bud_type) => {
|
||||
use crate::bluetooth::aacp::StemPressType;
|
||||
info!(
|
||||
"Received Stem Press: {:?} on {:?}",
|
||||
press_type, bud_type
|
||||
);
|
||||
if stem_control_clone.load(Ordering::Relaxed) {
|
||||
let controller = mc_clone.lock().await;
|
||||
match press_type {
|
||||
StemPressType::DoublePress => {
|
||||
info!("Double press detected, skipping to next track");
|
||||
controller.next_track().await;
|
||||
}
|
||||
StemPressType::TriplePress => {
|
||||
info!("Triple press detected, going to previous track");
|
||||
controller.previous_track().await;
|
||||
}
|
||||
_ => {
|
||||
debug!("Unhandled stem press type: {:?}", press_type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("Stem control disabled, ignoring stem press event");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("Received unhandled AACP event: {:?}", event);
|
||||
let _ = ui_tx_clone.send(BluetoothUIMessage::AACPUIEvent(
|
||||
|
||||
Reference in New Issue
Block a user