···66# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7788[dependencies]
99+dbus = "0.9.7"
910nu-plugin = "0.89.0"
1011nu-protocol = { version = "0.89.0", features = ["plugin"] }
+78
src/client.rs
···11+use dbus::{blocking::LocalConnection, channel::{Channel, BusType}};
22+use nu_plugin::LabeledError;
33+use nu_protocol::Spanned;
44+55+use crate::config::{DbusClientConfig, DbusBusChoice};
66+77+/// Executes D-Bus actions on a connection, handling nushell types
88+pub struct DbusClient {
99+ config: DbusClientConfig,
1010+ conn: LocalConnection,
1111+}
1212+1313+impl DbusClient {
1414+ pub fn new(config: DbusClientConfig) -> Result<DbusClient, LabeledError> {
1515+ // Try to connect to the correct D-Bus destination, as specified in the config
1616+ let channel = match &config.bus_choice.item {
1717+ DbusBusChoice::Session => Channel::get_private(BusType::Session),
1818+ DbusBusChoice::System => Channel::get_private(BusType::System),
1919+ DbusBusChoice::Started => Channel::get_private(BusType::Starter),
2020+ DbusBusChoice::Peer(address) => Channel::open_private(address),
2121+ DbusBusChoice::Bus(address) => Channel::open_private(address).and_then(|mut ch| {
2222+ ch.register()?;
2323+ Ok(ch)
2424+ }),
2525+ }.map_err(|err| {
2626+ LabeledError {
2727+ label: err.to_string(),
2828+ msg: "while connecting to D-Bus as specified here".into(),
2929+ span: Some(config.bus_choice.span),
3030+ }
3131+ })?;
3232+ Ok(DbusClient {
3333+ config,
3434+ conn: LocalConnection::from(channel)
3535+ })
3636+ }
3737+3838+ /// Call a D-Bus method and wait for the response
3939+ pub fn call(
4040+ &self,
4141+ dest: &Spanned<String>,
4242+ object: &Spanned<String>,
4343+ interface: &Spanned<String>,
4444+ method: &Spanned<String>,
4545+ ) -> Result<(), LabeledError> {
4646+ // TODO convert & return response
4747+ // TODO accept arguments
4848+ // Validate inputs before sending to the dbus lib so we don't panic
4949+ macro_rules! validate_with {
5050+ ($type:ty, $spanned:expr) => (<$type>::new(&$spanned.item).map_err(|msg| {
5151+ LabeledError {
5252+ label: msg,
5353+ msg: "this argument is incorrect".into(),
5454+ span: Some($spanned.span),
5555+ }
5656+ }))
5757+ }
5858+ let valid_dest = validate_with!(dbus::strings::BusName, dest)?;
5959+ let valid_object = validate_with!(dbus::strings::Path, object)?;
6060+ let valid_interface = validate_with!(dbus::strings::Interface, interface)?;
6161+ let valid_method = validate_with!(dbus::strings::Member, method)?;
6262+6363+ // Send method call
6464+ let proxy = self.conn.with_proxy(
6565+ valid_dest,
6666+ valid_object,
6767+ self.config.timeout.item
6868+ );
6969+ let () = proxy.method_call(valid_interface, valid_method, ()).map_err(|err| {
7070+ LabeledError {
7171+ label: err.to_string(),
7272+ msg: "while calling a D-Bus method".into(),
7373+ span: Some(self.config.span),
7474+ }
7575+ })?;
7676+ Ok(())
7777+ }
7878+}
+84
src/config.rs
···11+use std::time::Duration;
22+33+use nu_plugin::{EvaluatedCall, LabeledError};
44+use nu_protocol::{Spanned, Span};
55+66+/// General configuration related to the D-Bus client connection
77+#[derive(Debug, Clone)]
88+pub struct DbusClientConfig {
99+ pub span: Span,
1010+ pub bus_choice: Spanned<DbusBusChoice>,
1111+ pub timeout: Spanned<Duration>,
1212+}
1313+1414+/// Where to connect to the D-Bus server
1515+#[derive(Debug, Clone, Default)]
1616+pub enum DbusBusChoice {
1717+ /// Connect to the session bus
1818+ #[default]
1919+ Session,
2020+ /// Connect to the system bus
2121+ System,
2222+ /// Connect to the bus that started this process
2323+ Started,
2424+ /// Connect to a bus instance at the given address
2525+ Bus(String),
2626+ /// Connect to a non-bus D-Bus server at the given address (will not send Hello)
2727+ Peer(String),
2828+}
2929+3030+impl TryFrom<&EvaluatedCall> for DbusClientConfig {
3131+ type Error = LabeledError;
3232+3333+ fn try_from(call: &EvaluatedCall) -> Result<Self, Self::Error> {
3434+ let mut config = DbusClientConfig {
3535+ span: call.head,
3636+ bus_choice: Spanned { item: DbusBusChoice::default(), span: call.head },
3737+ timeout: Spanned { item: Duration::from_secs(2), span: call.head },
3838+ };
3939+4040+ // Handle recognized config args
4141+ for (name, value) in &call.named {
4242+ match &name.item[..] {
4343+ r#type @ ("session" | "system" | "started") => {
4444+ if value.is_none() || value.as_ref().is_some_and(|v| v.is_true()) {
4545+ let dest = match r#type {
4646+ "session" => DbusBusChoice::Session,
4747+ "system" => DbusBusChoice::System,
4848+ "started" => DbusBusChoice::Started,
4949+ _ => unreachable!()
5050+ };
5151+ config.bus_choice = Spanned { item: dest, span: name.span };
5252+ }
5353+ },
5454+ r#type @ ("bus" | "peer") => {
5555+ if let Some(value) = value {
5656+ let address = value.as_string()?;
5757+ let dest = match r#type {
5858+ "bus" => DbusBusChoice::Bus(address),
5959+ "peer" => DbusBusChoice::Peer(address),
6060+ _ => unreachable!()
6161+ };
6262+ config.bus_choice = Spanned { item: dest, span: value.span() };
6363+ }
6464+ },
6565+ "timeout" => {
6666+ if let Some(value) = value {
6767+ let nanos: u64 = value.as_duration()?.try_into().map_err(|_| {
6868+ LabeledError {
6969+ label: "Timeout must be a positive duration".into(),
7070+ msg: "invalid timeout specified here".into(),
7171+ span: Some(value.span()),
7272+ }
7373+ })?;
7474+ let item = Duration::from_nanos(nanos);
7575+ config.timeout = Spanned { item, span: value.span() };
7676+ }
7777+ },
7878+ _ => ()
7979+ }
8080+ }
8181+8282+ Ok(config)
8383+ }
8484+}
+74-5
src/main.rs
···11use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin, EvaluatedCall, LabeledError};
22-use nu_protocol::{PluginSignature, Value};
22+use nu_protocol::{PluginSignature, Value, SyntaxShape, PluginExample};
33+44+mod config;
55+mod client;
66+77+use config::*;
88+use client::*;
39410fn main() {
511 serve_plugin(&mut NuPluginDbus, MsgPackSerializer)
612}
7131414+/// The main plugin interface for nushell
815struct NuPluginDbus;
9161017impl Plugin for NuPluginDbus {
1118 fn signature(&self) -> Vec<PluginSignature> {
1219 vec![
1320 PluginSignature::build("dbus")
1414- .usage("Commands for interacting with D-Bus")
1515- .search_terms(vec!["dbus".into()])
1616- .category(nu_protocol::Category::Platform),
2121+ .is_dbus_command()
2222+ .usage("Commands for interacting with D-Bus"),
2323+ PluginSignature::build("dbus call")
2424+ .is_dbus_command()
2525+ .accepts_dbus_client_options()
2626+ .usage("Call a method and get its response")
2727+ .named("timeout", SyntaxShape::Duration, "How long to wait for a response", None)
2828+ .required_named("dest", SyntaxShape::String,
2929+ "The name of the connection to send the method to",
3030+ None)
3131+ .required("object", SyntaxShape::String,
3232+ "The path to the object to call the method on")
3333+ .required("interface", SyntaxShape::String,
3434+ "The name of the interface the method belongs to")
3535+ .required("method", SyntaxShape::String,
3636+ "The name of the method to send")
3737+ .plugin_examples(vec![
3838+ PluginExample {
3939+ example: "dbus call --dest=org.freedesktop.DBus \
4040+ /org/freedesktop/DBus org.freedesktop.DBus.Peer Ping".into(),
4141+ description: "Ping the D-Bus server itself".into(),
4242+ result: None
4343+ }
4444+ ]),
1745 ]
1846 }
1947···2149 &mut self,
2250 name: &str,
2351 call: &EvaluatedCall,
2424- input: &Value,
5252+ _input: &Value,
2553 ) -> Result<Value, LabeledError> {
2654 match name {
2755 "dbus" => Err(LabeledError {
···2957 msg: "add --help to see subcommands".into(),
3058 span: Some(call.head)
3159 }),
6060+6161+ "dbus call" => self.call(call),
32623363 _ => Err(LabeledError {
3464 label: "Plugin invoked with unknown command name".into(),
···3868 }
3969 }
4070}
7171+7272+/// For conveniently adding the base options to a dbus command
7373+trait DbusSignatureUtilExt {
7474+ fn is_dbus_command(self) -> Self;
7575+ fn accepts_dbus_client_options(self) -> Self;
7676+}
7777+7878+impl DbusSignatureUtilExt for PluginSignature {
7979+ fn is_dbus_command(self) -> Self {
8080+ self.search_terms(vec!["dbus".into()])
8181+ .category(nu_protocol::Category::Platform)
8282+ }
8383+8484+ fn accepts_dbus_client_options(self) -> Self {
8585+ self.switch("session", "Send to the session message bus (default)", None)
8686+ .switch("system", "Send to the system message bus", None)
8787+ .switch("started", "Send to the bus that started this process, if applicable", None)
8888+ .named("bus", SyntaxShape::String, "Send to the bus server at the given address", None)
8989+ .named("peer", SyntaxShape::String,
9090+ "Send to a non-bus D-Bus server at the given address. \
9191+ Will not call the Hello method on initialization.",
9292+ None)
9393+ }
9494+}
9595+9696+impl NuPluginDbus {
9797+ fn call(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
9898+ let config = DbusClientConfig::try_from(call)?;
9999+ let dbus = DbusClient::new(config)?;
100100+ dbus.call(
101101+ &call.get_flag("dest")?.unwrap(),
102102+ &call.req(0)?,
103103+ &call.req(1)?,
104104+ &call.req(2)?,
105105+ )?;
106106+ // TODO handle response
107107+ Ok(Value::nothing(call.head))
108108+ }
109109+}