Something went wrong. Try again.
Nushell plugin for interacting with D-Bus
Something went wrong. Try again.
14 kB · 399 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400use dbus::{ Message, arg::messageitem::MessageItem, channel::{BusType, Channel},};use nu_protocol::{LabeledError, Spanned, Value};
use crate::{ config::{DbusBusChoice, DbusClientConfig}, convert::to_message_item, dbus_type::DbusType, introspection::Node, pattern::Pattern,};
/// Executes D-Bus actions on a connection, handling nushell typespub struct DbusClient { config: DbusClientConfig, conn: Channel,}
// Convenience macros for error handlingmacro_rules! validate_with { ($type:ty, $spanned:expr) => { <$type>::new(&$spanned.item) .map_err(|msg| LabeledError::new("Invalid argument").with_label(msg, $spanned.span)) };}
impl DbusClient { pub fn new(config: DbusClientConfig) -> Result<DbusClient, LabeledError> { // Try to connect to the correct D-Bus destination, as specified in the config let channel = match &config.bus_choice.item { DbusBusChoice::Session => Channel::get_private(BusType::Session), DbusBusChoice::System => Channel::get_private(BusType::System), DbusBusChoice::Started => Channel::get_private(BusType::Starter), DbusBusChoice::Peer(address) => Channel::open_private(address), DbusBusChoice::Bus(address) => Channel::open_private(address).and_then(|mut ch| { ch.register()?; Ok(ch) }), } .map_err(|err| { LabeledError::new(err.to_string()).with_label( "while connecting to D-Bus as specified here", config.bus_choice.span, ) })?; Ok(DbusClient { config, conn: channel, }) }
fn error(&self, err: impl std::fmt::Display, msg: impl std::fmt::Display) -> LabeledError { LabeledError::new(err.to_string()).with_label(msg.to_string(), self.config.span) }
/// Introspect a D-Bus object pub fn introspect( &self, dest: &Spanned<String>, object: &Spanned<String>, ) -> Result<Node, LabeledError> { let context = "while introspecting a D-Bus method"; let valid_dest = validate_with!(dbus::strings::BusName, dest)?; let valid_object = validate_with!(dbus::strings::Path, object)?;
// Create the introspection method call let message = Message::new_method_call( valid_dest, valid_object, "org.freedesktop.DBus.Introspectable", "Introspect", ) .map_err(|err| self.error(err, context))?;
// Send and get the response let resp = self .conn .send_with_reply_and_block(message, self.config.timeout.item) .map_err(|err| self.error(err, context))?;
// Parse it to a Node let xml: &str = resp .get1() .ok_or_else(|| self.error("Introspect method returned the wrong type", context))?;
Node::from_xml(xml).map_err(|err| self.error(err, context)) }
/// Try to use introspection to get the signature of a method fn get_method_signature_by_introspection( &self, dest: &Spanned<String>, object: &Spanned<String>, interface: &Spanned<String>, method: &Spanned<String>, ) -> Result<Vec<DbusType>, LabeledError> { let node = self.introspect(dest, object)?;
if let Some(sig) = node.get_method_args_signature(&interface.item, &method.item) { DbusType::parse_all(&sig).map_err(|err| { LabeledError::new(format!( "while getting interface {:?} method {:?} signature: {}", interface.item, method.item, err )) .with_label( "try running with --no-introspect or --signature", self.config.span, ) }) } else { Err(LabeledError::new(format!( "Method {:?} not found on {:?}", method.item, interface.item )) .with_label("check that this method/interface is correct", method.span)) } }
/// Try to use introspection to get the signature of a property fn get_property_signature_by_introspection( &self, dest: &Spanned<String>, object: &Spanned<String>, interface: &Spanned<String>, property: &Spanned<String>, ) -> Result<Vec<DbusType>, LabeledError> { let node = self.introspect(dest, object)?;
if let Some(sig) = node.get_property_signature(&interface.item, &property.item) { DbusType::parse_all(sig).map_err(|err| { LabeledError::new(format!( "while getting interface {:?} property {:?} signature: {}", interface.item, property.item, err )) .with_label( "try running with --no-introspect or --signature", self.config.span, ) }) } else { Err(LabeledError::new(format!( "Property {:?} not found on {:?}", property.item, interface.item )) .with_label( "check that this property or interface is correct", property.span, )) } }
/// Call a D-Bus method and wait for the response pub fn call( &self, dest: &Spanned<String>, object: &Spanned<String>, interface: &Spanned<String>, method: &Spanned<String>, signature: Option<&Spanned<String>>, args: &[Value], ) -> Result<Vec<Value>, LabeledError> { let context = "while calling a D-Bus method";
// Validate inputs before sending to the dbus lib so we don't panic let valid_dest = validate_with!(dbus::strings::BusName, dest)?; let valid_object = validate_with!(dbus::strings::Path, object)?; let valid_interface = validate_with!(dbus::strings::Interface, interface)?; let valid_method = validate_with!(dbus::strings::Member, method)?;
// Parse the signature let mut valid_signature = signature .map(|s| { DbusType::parse_all(&s.item).map_err(|err| { LabeledError::new(err).with_label("in signature specified here", s.span) }) }) .transpose()?;
// If not provided, try introspection (unless disabled) if valid_signature.is_none() && self.config.introspect { match self.get_method_signature_by_introspection(dest, object, interface, method) { Ok(sig) => { valid_signature = Some(sig); } Err(err) => { eprintln!( "Warning: D-Bus introspection failed on {:?}. \ Use `--no-introspect` or pass `--signature` to silence this warning. \ Cause: {}", object.item, err ); } } }
if let Some(sig) = &valid_signature { if sig.len() != args.len() { self.error( format!("expected {} arguments, got {}", sig.len(), args.len()), context, ); } }
// Construct the method call message let mut message = Message::new_method_call(valid_dest, valid_object, valid_interface, valid_method) .map_err(|err| self.error(err, context))?;
// Convert the args to message items let sigs_iter = valid_signature .iter() .flatten() .map(Some) .chain(std::iter::repeat(None)); for (val, sig) in args.iter().zip(sigs_iter) { message = message.append1(to_message_item(val, sig)?); }
// Send it on the channel and get the response let resp = self .conn .send_with_reply_and_block(message, self.config.timeout.item) .map_err(|err| self.error(err, context))?;
crate::convert::from_message(&resp, self.config.span) .map_err(|err| self.error(err, context)) }
/// Get a D-Bus property from the given object pub fn get( &self, dest: &Spanned<String>, object: &Spanned<String>, interface: &Spanned<String>, property: &Spanned<String>, ) -> Result<Value, LabeledError> { let interface_val = Value::string(&interface.item, interface.span); let property_val = Value::string(&property.item, property.span);
self.call( dest, object, &Spanned { item: "org.freedesktop.DBus.Properties".into(), span: self.config.span, }, &Spanned { item: "Get".into(), span: self.config.span, }, Some(&Spanned { item: "ss".into(), span: self.config.span, }), &[interface_val, property_val], ) .map(|val| val.into_iter().nth(0).unwrap_or_default()) }
/// Get all D-Bus properties from the given object pub fn get_all( &self, dest: &Spanned<String>, object: &Spanned<String>, interface: &Spanned<String>, ) -> Result<Value, LabeledError> { let interface_val = Value::string(&interface.item, interface.span);
self.call( dest, object, &Spanned { item: "org.freedesktop.DBus.Properties".into(), span: self.config.span, }, &Spanned { item: "GetAll".into(), span: self.config.span, }, Some(&Spanned { item: "s".into(), span: self.config.span, }), &[interface_val], ) .map(|val| val.into_iter().nth(0).unwrap_or_default()) }
/// Set a D-Bus property on the given object pub fn set( &self, dest: &Spanned<String>, object: &Spanned<String>, interface: &Spanned<String>, property: &Spanned<String>, signature: Option<&Spanned<String>>, value: &Value, ) -> Result<(), LabeledError> { let context = "while setting a D-Bus property";
// Validate inputs before sending to the dbus lib so we don't panic let valid_dest = validate_with!(dbus::strings::BusName, dest)?; let valid_object = validate_with!(dbus::strings::Path, object)?;
// Parse the signature let mut valid_signature = signature .map(|s| { DbusType::parse_all(&s.item).map_err(|err| { LabeledError::new(err).with_label("in signature specified here", s.span) }) }) .transpose()?;
// If not provided, try introspection (unless disabled) if valid_signature.is_none() && self.config.introspect { match self.get_property_signature_by_introspection(dest, object, interface, property) { Ok(sig) => { valid_signature = Some(sig); } Err(err) => { eprintln!( "Warning: D-Bus introspection failed on {:?}. \ Use `--no-introspect` or pass `--signature` to silence this warning. \ Cause: {}", object.item, err ); } } }
if let Some(sig) = &valid_signature { if sig.len() != 1 { self.error( format!( "expected single object signature, but there are {}", sig.len() ), context, ); } }
// Construct the method call message let message = Message::new_method_call( valid_dest, valid_object, "org.freedesktop.DBus.Properties", "Set", ) .map_err(|err| self.error(err, context))? .append2(&interface.item, &property.item) .append1( // Box it in a variant as required for property setting MessageItem::Variant(Box::new(to_message_item( value, valid_signature.as_ref().map(|s| &s[0]), )?)), );
// Send it on the channel and get the response self.conn .send_with_reply_and_block(message, self.config.timeout.item) .map_err(|err| self.error(err, context))?;
Ok(()) }
pub fn list(&self, pattern: Option<&Pattern>) -> Result<Vec<String>, LabeledError> { let context = "while listing D-Bus connection names";
let message = Message::new_method_call( "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames", ) .map_err(|err| self.error(err, context))?;
self.conn .send_with_reply_and_block(message, self.config.timeout.item) .map_err(|err| self.error(err, context)) .and_then(|reply| reply.read1().map_err(|err| self.error(err, context))) .map(|names: Vec<String>| { // Filter the names by the pattern if let Some(pattern) = pattern { names .into_iter() .filter(|name| pattern.is_match(name)) .collect() } else { names } }) }}