Loading system/gd/rust/linux/client/src/callbacks.rs +45 −39 Original line number Diff line number Diff line Loading @@ -1312,6 +1312,7 @@ impl RPCProxy for QACallback { pub(crate) struct MediaCallback { objpath: String, context: Arc<Mutex<ClientContext>>, dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, Loading @@ -1320,10 +1321,11 @@ pub(crate) struct MediaCallback { impl MediaCallback { pub(crate) fn new( objpath: String, context: Arc<Mutex<ClientContext>>, dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, ) -> Self { Self { objpath, dbus_connection, dbus_crossroads } Self { objpath, context, dbus_connection, dbus_crossroads } } } Loading Loading @@ -1351,6 +1353,9 @@ impl IBluetoothMediaCallback for MediaCallback { pkt_status_in_hex: String, pkt_status_in_binary: String, ) { // Invoke run_callback so that the callback can be handled through // ForegroundActions::RunCallback in main.rs. self.context.lock().unwrap().run_callback(Box::new(move |_context| { let wbs_dump = if active && wbs { let mut to_split_binary = pkt_status_in_binary.clone(); let mut wrapped_binary = String::new(); Loading Loading @@ -1391,6 +1396,7 @@ impl IBluetoothMediaCallback for MediaCallback { if wbs { "mSBC" } else { "CVSD" }, wbs_dump ); })); } } Loading system/gd/rust/linux/client/src/command_handler.rs +7 −4 Original line number Diff line number Diff line Loading @@ -366,29 +366,32 @@ impl CommandHandler { } /// Entry point for command and arguments pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) { pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) -> bool { // Ignore empty line match command { "" => {} "" => false, _ => match self.command_options.get(command) { Some(cmd) => { let rules = cmd.rules.clone(); match (cmd.function_pointer)(self, &args) { Ok(()) => {} Ok(()) => true, Err(CommandError::InvalidArgs) => { print_error!("Invalid arguments. Usage:\n{}", rules.join("\n")); false } Err(CommandError::Failed(msg)) => { print_error!("Command failed: {}", msg); false } } } None => { println!("'{}' is an invalid command!", command); self.cmd_help(&args).ok(); false } }, }; } } fn lock_context(&self) -> std::sync::MutexGuard<ClientContext> { Loading system/gd/rust/linux/client/src/main.rs +110 −38 Original line number Diff line number Diff line Loading @@ -2,12 +2,14 @@ use clap::{value_t, App, Arg}; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use std::time::Duration; use dbus::channel::MatchingReceiver; use dbus::message::MatchRule; use dbus::nonblock::SyncConnection; use dbus_crossroads::Crossroads; use tokio::sync::mpsc; use tokio::time::timeout; use crate::bt_adv::AdvSet; use crate::bt_gatt::GattClientContext; Loading Loading @@ -140,6 +142,9 @@ pub(crate) struct ClientContext { /// The handle of the SDP record for MPS (Multi-Profile Specification). mps_sdp_handle: Option<i32>, /// The set of client commands that need to wait for callbacks. client_commands_with_callbacks: Vec<String>, } impl ClientContext { Loading @@ -148,6 +153,7 @@ impl ClientContext { dbus_crossroads: Arc<Mutex<Crossroads>>, tx: mpsc::Sender<ForegroundActions>, is_restricted: bool, client_commands_with_callbacks: Vec<String>, ) -> ClientContext { // Manager interface is almost always available but adapter interface // requires that the specific adapter is enabled. Loading Loading @@ -187,6 +193,7 @@ impl ClientContext { gatt_client_context: GattClientContext::new(), socket_test_schedule: None, mps_sdp_handle: None, client_commands_with_callbacks, } } Loading Loading @@ -305,10 +312,24 @@ enum ForegroundActions { fn main() -> Result<(), Box<dyn std::error::Error>> { let matches = App::new("btclient") .arg(Arg::with_name("restricted").long("restricted").takes_value(false)) .arg(Arg::with_name("command").short("c").long("command").takes_value(true)) .arg( Arg::with_name("command") .short("c") .long("command") .takes_value(true) .help("Executes a non-interactive command"), ) .arg( Arg::with_name("timeout") .short("t") .long("timeout") .takes_value(true) .help("Specify a timeout in seconds for a non-interactive command"), ) .get_matches(); let command = value_t!(matches, "command", String); let is_restricted = matches.is_present("restricted"); let timeout_secs = value_t!(matches, "timeout", u64); topstack::get_runtime().block_on(async move { // Connect to D-Bus system bus. Loading Loading @@ -341,12 +362,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { // Accept foreground actions with mpsc let (tx, rx) = mpsc::channel::<ForegroundActions>(10); // Include the commands // (1) that will be run as non-interactive client commands, and // (2) that will need to wait for the callbacks to complete. let client_commands_with_callbacks = vec!["media".to_string()]; // Create the context needed for handling commands let context = Arc::new(Mutex::new(ClientContext::new( conn.clone(), cr.clone(), tx.clone(), is_restricted, client_commands_with_callbacks, ))); // Check if manager interface is valid. We only print some help text before failing on the Loading Loading @@ -385,36 +412,57 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { } } let mut handler = CommandHandler::new(context.clone()); // Allow command line arguments to be read match command { Ok(command) => { let mut iter = command.split(' ').map(String::from); handler.process_cmd_line( &iter.next().unwrap_or(String::from("")), &iter.collect::<Vec<String>>(), ); let handler = CommandHandler::new(context.clone()); if let Ok(_) = command { // Timeout applies only to non-interactive commands. if let Ok(timeout_secs) = timeout_secs { let timeout_duration = Duration::from_secs(timeout_secs); match timeout( timeout_duration, handle_client_command(handler, tx, rx, context, command), ) .await { Ok(_) => { return Result::Ok(()); } _ => { start_interactive_shell(handler, tx, rx, context).await?; Err(_) => { return Result::Err("btclient timeout".into()); } }; } } // There are two scenarios in which handle_client_command is run without a timeout. // - Interactive commands: none of these commands require a timeout. // - Non-interactive commands that have not specified a timeout. handle_client_command(handler, tx, rx, context, command).await?; return Result::Ok(()); }) } async fn start_interactive_shell( // If btclient runs without command arguments, the interactive shell // actions are performed. // If btclient runs with command arguments, the command is executed // once. There are two cases to exit. // Case 1: if the command does not need a callback, e.g., "help", // it will exit after running handler.process_cmd_line(). // Case 2: if the command needs a callback, e.g., "media log", // it will exit after the callback has been run in the arm // of ForegroundActions::RunCallback(callback). async fn handle_client_command( mut handler: CommandHandler, tx: mpsc::Sender<ForegroundActions>, mut rx: mpsc::Receiver<ForegroundActions>, context: Arc<Mutex<ClientContext>>, command: Result<String, clap::Error>, ) -> Result<(), Box<dyn std::error::Error>> { let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1)); // If there are no command arguments, start the interactive shell. if let Err(_) = command { let command_rule_list = handler.get_command_rule_list().clone(); let context_for_closure = context.clone(); let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1)); // Async task to keep reading new lines from user let semaphore = semaphore_fg.clone(); let editor = AsyncEditor::new(command_rule_list, context_for_closure) Loading @@ -434,6 +482,7 @@ async fn start_interactive_shell( let _ = tx.send(ForegroundActions::Readline(result)).await; } }); } 'readline: loop { let m = rx.recv().await; Loading @@ -458,6 +507,11 @@ async fn start_interactive_shell( } ForegroundActions::RunCallback(callback) => { callback(context.clone()); // Break the loop as a non-interactive command is completed. if let Ok(_) = command { break; } } // Once adapter is ready, register callbacks, get the address and mark it as ready ForegroundActions::RegisterAdapterCallback(adapter) => { Loading Loading @@ -619,6 +673,7 @@ async fn start_interactive_shell( .rpc .register_callback(Box::new(MediaCallback::new( media_cb_objpath, context.clone(), dbus_connection.clone(), dbus_crossroads.clone(), ))) Loading @@ -630,6 +685,23 @@ async fn start_interactive_shell( context.lock().unwrap().update_bonded_devices(); print_info!("Adapter {} is ready", adapter_address); // Run the command with the command arguments as the client is // non-interactive. if let Some(command) = command.as_ref().ok() { let mut iter = command.split(' ').map(String::from); let first = iter.next().unwrap_or(String::from("")); if !handler.process_cmd_line(&first, &iter.collect::<Vec<String>>()) { // Break immediately if the command fails to execute. break; } // Break the loop immediately if there is no callback // to wait for. if !context.lock().unwrap().client_commands_with_callbacks.contains(&first) { break; } } } ForegroundActions::Readline(result) => match result { Err(rustyline::error::ReadlineError::Interrupted) => { Loading Loading
system/gd/rust/linux/client/src/callbacks.rs +45 −39 Original line number Diff line number Diff line Loading @@ -1312,6 +1312,7 @@ impl RPCProxy for QACallback { pub(crate) struct MediaCallback { objpath: String, context: Arc<Mutex<ClientContext>>, dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, Loading @@ -1320,10 +1321,11 @@ pub(crate) struct MediaCallback { impl MediaCallback { pub(crate) fn new( objpath: String, context: Arc<Mutex<ClientContext>>, dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, ) -> Self { Self { objpath, dbus_connection, dbus_crossroads } Self { objpath, context, dbus_connection, dbus_crossroads } } } Loading Loading @@ -1351,6 +1353,9 @@ impl IBluetoothMediaCallback for MediaCallback { pkt_status_in_hex: String, pkt_status_in_binary: String, ) { // Invoke run_callback so that the callback can be handled through // ForegroundActions::RunCallback in main.rs. self.context.lock().unwrap().run_callback(Box::new(move |_context| { let wbs_dump = if active && wbs { let mut to_split_binary = pkt_status_in_binary.clone(); let mut wrapped_binary = String::new(); Loading Loading @@ -1391,6 +1396,7 @@ impl IBluetoothMediaCallback for MediaCallback { if wbs { "mSBC" } else { "CVSD" }, wbs_dump ); })); } } Loading
system/gd/rust/linux/client/src/command_handler.rs +7 −4 Original line number Diff line number Diff line Loading @@ -366,29 +366,32 @@ impl CommandHandler { } /// Entry point for command and arguments pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) { pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) -> bool { // Ignore empty line match command { "" => {} "" => false, _ => match self.command_options.get(command) { Some(cmd) => { let rules = cmd.rules.clone(); match (cmd.function_pointer)(self, &args) { Ok(()) => {} Ok(()) => true, Err(CommandError::InvalidArgs) => { print_error!("Invalid arguments. Usage:\n{}", rules.join("\n")); false } Err(CommandError::Failed(msg)) => { print_error!("Command failed: {}", msg); false } } } None => { println!("'{}' is an invalid command!", command); self.cmd_help(&args).ok(); false } }, }; } } fn lock_context(&self) -> std::sync::MutexGuard<ClientContext> { Loading
system/gd/rust/linux/client/src/main.rs +110 −38 Original line number Diff line number Diff line Loading @@ -2,12 +2,14 @@ use clap::{value_t, App, Arg}; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use std::time::Duration; use dbus::channel::MatchingReceiver; use dbus::message::MatchRule; use dbus::nonblock::SyncConnection; use dbus_crossroads::Crossroads; use tokio::sync::mpsc; use tokio::time::timeout; use crate::bt_adv::AdvSet; use crate::bt_gatt::GattClientContext; Loading Loading @@ -140,6 +142,9 @@ pub(crate) struct ClientContext { /// The handle of the SDP record for MPS (Multi-Profile Specification). mps_sdp_handle: Option<i32>, /// The set of client commands that need to wait for callbacks. client_commands_with_callbacks: Vec<String>, } impl ClientContext { Loading @@ -148,6 +153,7 @@ impl ClientContext { dbus_crossroads: Arc<Mutex<Crossroads>>, tx: mpsc::Sender<ForegroundActions>, is_restricted: bool, client_commands_with_callbacks: Vec<String>, ) -> ClientContext { // Manager interface is almost always available but adapter interface // requires that the specific adapter is enabled. Loading Loading @@ -187,6 +193,7 @@ impl ClientContext { gatt_client_context: GattClientContext::new(), socket_test_schedule: None, mps_sdp_handle: None, client_commands_with_callbacks, } } Loading Loading @@ -305,10 +312,24 @@ enum ForegroundActions { fn main() -> Result<(), Box<dyn std::error::Error>> { let matches = App::new("btclient") .arg(Arg::with_name("restricted").long("restricted").takes_value(false)) .arg(Arg::with_name("command").short("c").long("command").takes_value(true)) .arg( Arg::with_name("command") .short("c") .long("command") .takes_value(true) .help("Executes a non-interactive command"), ) .arg( Arg::with_name("timeout") .short("t") .long("timeout") .takes_value(true) .help("Specify a timeout in seconds for a non-interactive command"), ) .get_matches(); let command = value_t!(matches, "command", String); let is_restricted = matches.is_present("restricted"); let timeout_secs = value_t!(matches, "timeout", u64); topstack::get_runtime().block_on(async move { // Connect to D-Bus system bus. Loading Loading @@ -341,12 +362,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { // Accept foreground actions with mpsc let (tx, rx) = mpsc::channel::<ForegroundActions>(10); // Include the commands // (1) that will be run as non-interactive client commands, and // (2) that will need to wait for the callbacks to complete. let client_commands_with_callbacks = vec!["media".to_string()]; // Create the context needed for handling commands let context = Arc::new(Mutex::new(ClientContext::new( conn.clone(), cr.clone(), tx.clone(), is_restricted, client_commands_with_callbacks, ))); // Check if manager interface is valid. We only print some help text before failing on the Loading Loading @@ -385,36 +412,57 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { } } let mut handler = CommandHandler::new(context.clone()); // Allow command line arguments to be read match command { Ok(command) => { let mut iter = command.split(' ').map(String::from); handler.process_cmd_line( &iter.next().unwrap_or(String::from("")), &iter.collect::<Vec<String>>(), ); let handler = CommandHandler::new(context.clone()); if let Ok(_) = command { // Timeout applies only to non-interactive commands. if let Ok(timeout_secs) = timeout_secs { let timeout_duration = Duration::from_secs(timeout_secs); match timeout( timeout_duration, handle_client_command(handler, tx, rx, context, command), ) .await { Ok(_) => { return Result::Ok(()); } _ => { start_interactive_shell(handler, tx, rx, context).await?; Err(_) => { return Result::Err("btclient timeout".into()); } }; } } // There are two scenarios in which handle_client_command is run without a timeout. // - Interactive commands: none of these commands require a timeout. // - Non-interactive commands that have not specified a timeout. handle_client_command(handler, tx, rx, context, command).await?; return Result::Ok(()); }) } async fn start_interactive_shell( // If btclient runs without command arguments, the interactive shell // actions are performed. // If btclient runs with command arguments, the command is executed // once. There are two cases to exit. // Case 1: if the command does not need a callback, e.g., "help", // it will exit after running handler.process_cmd_line(). // Case 2: if the command needs a callback, e.g., "media log", // it will exit after the callback has been run in the arm // of ForegroundActions::RunCallback(callback). async fn handle_client_command( mut handler: CommandHandler, tx: mpsc::Sender<ForegroundActions>, mut rx: mpsc::Receiver<ForegroundActions>, context: Arc<Mutex<ClientContext>>, command: Result<String, clap::Error>, ) -> Result<(), Box<dyn std::error::Error>> { let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1)); // If there are no command arguments, start the interactive shell. if let Err(_) = command { let command_rule_list = handler.get_command_rule_list().clone(); let context_for_closure = context.clone(); let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1)); // Async task to keep reading new lines from user let semaphore = semaphore_fg.clone(); let editor = AsyncEditor::new(command_rule_list, context_for_closure) Loading @@ -434,6 +482,7 @@ async fn start_interactive_shell( let _ = tx.send(ForegroundActions::Readline(result)).await; } }); } 'readline: loop { let m = rx.recv().await; Loading @@ -458,6 +507,11 @@ async fn start_interactive_shell( } ForegroundActions::RunCallback(callback) => { callback(context.clone()); // Break the loop as a non-interactive command is completed. if let Ok(_) = command { break; } } // Once adapter is ready, register callbacks, get the address and mark it as ready ForegroundActions::RegisterAdapterCallback(adapter) => { Loading Loading @@ -619,6 +673,7 @@ async fn start_interactive_shell( .rpc .register_callback(Box::new(MediaCallback::new( media_cb_objpath, context.clone(), dbus_connection.clone(), dbus_crossroads.clone(), ))) Loading @@ -630,6 +685,23 @@ async fn start_interactive_shell( context.lock().unwrap().update_bonded_devices(); print_info!("Adapter {} is ready", adapter_address); // Run the command with the command arguments as the client is // non-interactive. if let Some(command) = command.as_ref().ok() { let mut iter = command.split(' ').map(String::from); let first = iter.next().unwrap_or(String::from("")); if !handler.process_cmd_line(&first, &iter.collect::<Vec<String>>()) { // Break immediately if the command fails to execute. break; } // Break the loop immediately if there is no callback // to wait for. if !context.lock().unwrap().client_commands_with_callbacks.contains(&first) { break; } } } ForegroundActions::Readline(result) => match result { Err(rustyline::error::ReadlineError::Interrupted) => { Loading