···11+mod call;
22+mod get;
33+mod get_all;
44+mod introspect;
55+mod list;
66+mod main;
77+mod set;
88+99+pub use call::Call;
1010+pub use get::Get;
1111+pub use get_all::GetAll;
1212+pub use introspect::Introspect;
1313+pub use list::List;
1414+pub use main::Main;
1515+pub use set::Set;
+95
src/commands/set.rs
···11+use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
22+use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value};
33+44+use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt};
55+66+pub struct Set;
77+88+impl SimplePluginCommand for Set {
99+ type Plugin = crate::NuPluginDbus;
1010+1111+ fn name(&self) -> &str {
1212+ "dbus set"
1313+ }
1414+1515+ fn signature(&self) -> Signature {
1616+ Signature::build(self.name())
1717+ .dbus_command()
1818+ .accepts_dbus_client_options()
1919+ .accepts_timeout()
2020+ .input_output_type(Type::Nothing, Type::Nothing)
2121+ .named(
2222+ "signature",
2323+ SyntaxShape::String,
2424+ "Signature of the value to set, in D-Bus format.\n \
2525+ If not provided, it will be determined from introspection.\n \
2626+ If --no-introspect is specified and this is not provided, it will \
2727+ be guessed (poorly)",
2828+ None,
2929+ )
3030+ .required_named(
3131+ "dest",
3232+ SyntaxShape::String,
3333+ "The name of the connection to write the property on",
3434+ None,
3535+ )
3636+ .required(
3737+ "object",
3838+ SyntaxShape::String,
3939+ "The path to the object to write the property on",
4040+ )
4141+ .required(
4242+ "interface",
4343+ SyntaxShape::String,
4444+ "The name of the interface the property belongs to",
4545+ )
4646+ .required(
4747+ "property",
4848+ SyntaxShape::String,
4949+ "The name of the property to write",
5050+ )
5151+ .required(
5252+ "value",
5353+ SyntaxShape::Any,
5454+ "The value to write to the property",
5555+ )
5656+ }
5757+5858+ fn usage(&self) -> &str {
5959+ "Set a D-Bus property"
6060+ }
6161+6262+ fn search_terms(&self) -> Vec<&str> {
6363+ vec!["dbus", "property", "write", "put"]
6464+ }
6565+6666+ fn examples(&self) -> Vec<Example> {
6767+ vec![Example {
6868+ example: "dbus set --dest=org.mpris.MediaPlayer2.spotify \
6969+ /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player \
7070+ Volume 0.5",
7171+ description: "Set the volume of Spotify to 50%",
7272+ result: None,
7373+ }]
7474+ }
7575+7676+ fn run(
7777+ &self,
7878+ _plugin: &Self::Plugin,
7979+ _engine: &EngineInterface,
8080+ call: &EvaluatedCall,
8181+ _input: &Value,
8282+ ) -> Result<Value, LabeledError> {
8383+ let config = DbusClientConfig::try_from(call)?;
8484+ let dbus = DbusClient::new(config)?;
8585+ dbus.set(
8686+ &call.get_flag("dest")?.unwrap(),
8787+ &call.req(0)?,
8888+ &call.req(1)?,
8989+ &call.req(2)?,
9090+ call.get_flag("signature")?.as_ref(),
9191+ &call.req(3)?,
9292+ )?;
9393+ Ok(Value::nothing(call.head))
9494+ }
9595+}
+6-8
src/config.rs
···11use std::time::Duration;
2233-use nu_plugin::{EvaluatedCall, LabeledError};
44-use nu_protocol::{Span, Spanned};
33+use nu_plugin::EvaluatedCall;
44+use nu_protocol::{LabeledError, Span, Spanned};
5566/// General configuration related to the D-Bus client connection
77#[derive(Debug, Clone)]
···8181 }
8282 "timeout" => {
8383 if let Some(value) = value {
8484- let nanos: u64 =
8585- value.as_duration()?.try_into().map_err(|_| LabeledError {
8686- label: "Timeout must be a positive duration".into(),
8787- msg: "invalid timeout specified here".into(),
8888- span: Some(value.span()),
8989- })?;
8484+ let nanos: u64 = value.as_duration()?.try_into().map_err(|_| {
8585+ LabeledError::new("Timeout must be a positive duration")
8686+ .with_label("invalid timeout specified here", value.span())
8787+ })?;
9088 let item = Duration::from_nanos(nanos);
9189 config.timeout = Spanned {
9290 item,
+28-33
src/convert.rs
···55 },
66 Message, Signature,
77};
88-use nu_plugin::LabeledError;
99-use nu_protocol::{Record, Span, Value};
88+use nu_protocol::{LabeledError, Record, Span, Value};
109use std::str::FromStr;
11101211use crate::dbus_type::DbusType;
···101100 // Report errors from conversion. Error must support Display
102101 macro_rules! try_convert {
103102 ($result_expr:expr) => {
104104- $result_expr.map_err(|err| LabeledError {
105105- label: format!(
103103+ $result_expr.map_err(|err| {
104104+ LabeledError::new(format!(
106105 "Failed to convert value to the D-Bus `{:?}` type",
107106 expected_type.unwrap()
108108- ),
109109- msg: err.to_string(),
110110- span: Some(value.span()),
107107+ ))
108108+ .with_label(err.to_string(), value.span())
111109 })?
112110 };
113111 }
···209207 // Struct
210208 (Value::List { vals, .. }, Some(DbusType::Struct(types))) => {
211209 if vals.len() != types.len() {
212212- return Err(LabeledError {
213213- label: format!(
214214- "expected struct with {} element(s) ({:?})",
215215- types.len(),
216216- types
217217- ),
218218- msg: format!("this list has {} element(s) instead", vals.len()),
219219- span: Some(value.span()),
220220- });
210210+ return Err(LabeledError::new(format!(
211211+ "expected struct with {} element(s) ({:?})",
212212+ types.len(),
213213+ types
214214+ ))
215215+ .with_label(
216216+ format!("this list has {} element(s) instead", vals.len()),
217217+ value.span(),
218218+ ));
221219 }
222220 let items = vals
223221 .iter()
···257255 ))),
258256259257 // Value not compatible with expected type
260260- (other_value, Some(expectation)) => Err(LabeledError {
261261- label: format!(
262262- "`{}` can not be converted to the D-Bus `{:?}` type",
263263- other_value.get_type(),
264264- expectation
265265- ),
266266- msg: format!("expected a `{:?}` here", expectation),
267267- span: Some(other_value.span()),
268268- }),
258258+ (other_value, Some(expectation)) => Err(LabeledError::new(format!(
259259+ "`{}` can not be converted to the D-Bus `{:?}` type",
260260+ other_value.get_type(),
261261+ expectation
262262+ ))
263263+ .with_label(
264264+ format!("expected a `{:?}` here", expectation),
265265+ other_value.span(),
266266+ )),
269267270268 // Automatic types (with no type expectation)
271269 (Value::String { .. }, None) => to_message_item(value, Some(&DbusType::String)),
···283281 ),
284282285283 // No expected type, but can't handle this type
286286- _ => Err(LabeledError {
287287- label: format!(
288288- "can not use values of type `{}` in D-Bus calls",
289289- value.get_type()
290290- ),
291291- msg: "use a supported type here instead".into(),
292292- span: Some(value.span()),
293293- }),
284284+ _ => Err(LabeledError::new(format!(
285285+ "can not use values of type `{}` in D-Bus calls",
286286+ value.get_type()
287287+ ))
288288+ .with_label("use a supported type here instead", value.span())),
294289 }
295290}
+14-335
src/main.rs
···11-use nu_plugin::{
22- serve_plugin, EngineInterface, EvaluatedCall, LabeledError, MsgPackSerializer, Plugin,
33-};
44-use nu_protocol::{PluginExample, PluginSignature, Span, SyntaxShape, Type, Value};
11+use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin, PluginCommand};
22+use nu_protocol::SyntaxShape;
5364mod client;
55+mod commands;
76mod config;
87mod convert;
98mod dbus_type;
109mod introspection;
1110mod pattern;
12111313-use client::*;
1414-use config::*;
1515-1616-use crate::pattern::Pattern;
1717-1812fn main() {
1913 serve_plugin(&NuPluginDbus, MsgPackSerializer)
2014}
21152216/// The main plugin interface for nushell
2323-struct NuPluginDbus;
1717+pub struct NuPluginDbus;
24182519impl Plugin for NuPluginDbus {
2626- fn signature(&self) -> Vec<PluginSignature> {
2727- macro_rules! str {
2828- ($s:expr) => {
2929- Value::string($s, Span::unknown())
3030- };
3131- }
2020+ fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
3221 vec![
3333- PluginSignature::build("dbus")
3434- .dbus_command()
3535- .usage("Commands for interacting with D-Bus"),
3636- PluginSignature::build("dbus introspect")
3737- .dbus_command()
3838- .accepts_dbus_client_options()
3939- .accepts_timeout()
4040- .usage("Introspect a D-Bus object")
4141- .input_output_type(Type::Nothing, Type::Record(vec![]))
4242- .extra_usage("Returns information about available nodes, interfaces, methods, \
4343- signals, and properties on the given object path")
4444- .required_named("dest", SyntaxShape::String,
4545- "The name of the connection that owns the object",
4646- None)
4747- .required("object", SyntaxShape::String,
4848- "The path to the object to introspect")
4949- .plugin_examples(vec![
5050- PluginExample {
5151- example: "dbus introspect --dest=org.mpris.MediaPlayer2.spotify \
5252- /org/mpris/MediaPlayer2 | explore".into(),
5353- description: "Look at the MPRIS2 interfaces exposed by Spotify".into(),
5454- result: None,
5555- },
5656- PluginExample {
5757- example: "dbus introspect --dest=org.kde.plasmashell \
5858- /org/kde/osdService | get interfaces | \
5959- where name == org.kde.osdService | get 0.methods".into(),
6060- description: "Get methods exposed by KDE Plasma's on-screen display \
6161- service".into(),
6262- result: None,
6363- },
6464- PluginExample {
6565- example: "dbus introspect --dest=org.kde.KWin / | get children | \
6666- select name".into(),
6767- description: "List objects exposed by KWin".into(),
6868- result: None,
6969- },
7070- ]),
7171- PluginSignature::build("dbus call")
7272- .dbus_command()
7373- .accepts_dbus_client_options()
7474- .accepts_timeout()
7575- .usage("Call a method and get its response")
7676- .extra_usage("Returns an array if the method call returns more than one value.")
7777- .input_output_type(Type::Nothing, Type::Any)
7878- .named("signature", SyntaxShape::String,
7979- "Signature of the arguments to send, in D-Bus format.\n \
8080- If not provided, they will be determined from introspection.\n \
8181- If --no-introspect is specified and this is not provided, they will \
8282- be guessed (poorly)", None)
8383- .switch("no-flatten",
8484- "Always return a list of all return values", None)
8585- .switch("no-introspect",
8686- "Don't use introspection to determine the correct argument signature", None)
8787- .required_named("dest", SyntaxShape::String,
8888- "The name of the connection to send the method to",
8989- None)
9090- .required("object", SyntaxShape::String,
9191- "The path to the object to call the method on")
9292- .required("interface", SyntaxShape::String,
9393- "The name of the interface the method belongs to")
9494- .required("method", SyntaxShape::String,
9595- "The name of the method to send")
9696- .rest("args", SyntaxShape::Any,
9797- "Arguments to send with the method call")
9898- .plugin_examples(vec![
9999- PluginExample {
100100- example: "dbus call --dest=org.freedesktop.DBus \
101101- /org/freedesktop/DBus org.freedesktop.DBus.Peer Ping".into(),
102102- description: "Ping the D-Bus server itself".into(),
103103- result: None
104104- },
105105- PluginExample {
106106- example: "dbus call --dest=org.freedesktop.Notifications \
107107- /org/freedesktop/Notifications org.freedesktop.Notifications \
108108- Notify \"Floppy disks\" 0 \"media-floppy\" \"Rarely seen\" \
109109- \"But sometimes still used\" [] {} 5000".into(),
110110- description: "Show a notification on the desktop for 5 seconds".into(),
111111- result: None
112112- },
113113- ]),
114114- PluginSignature::build("dbus get")
115115- .dbus_command()
116116- .accepts_dbus_client_options()
117117- .accepts_timeout()
118118- .usage("Get a D-Bus property")
119119- .input_output_type(Type::Nothing, Type::Any)
120120- .required_named("dest", SyntaxShape::String,
121121- "The name of the connection to read the property from",
122122- None)
123123- .required("object", SyntaxShape::String,
124124- "The path to the object to read the property from")
125125- .required("interface", SyntaxShape::String,
126126- "The name of the interface the property belongs to")
127127- .required("property", SyntaxShape::String,
128128- "The name of the property to read")
129129- .plugin_examples(vec![
130130- PluginExample {
131131- example: "dbus get --dest=org.mpris.MediaPlayer2.spotify \
132132- /org/mpris/MediaPlayer2 \
133133- org.mpris.MediaPlayer2.Player Metadata".into(),
134134- description: "Get the currently playing song in Spotify".into(),
135135- result: Some(Value::record(nu_protocol::record!(
136136- "xesam:title" => str!("Birdie"),
137137- "xesam:artist" => Value::list(vec![
138138- str!("LOVE PSYCHEDELICO")
139139- ], Span::unknown()),
140140- "xesam:album" => str!("Love Your Love"),
141141- "xesam:url" => str!("https://open.spotify.com/track/51748BvzeeMs4PIdPuyZmv"),
142142- ), Span::unknown()))
143143- },
144144- ]),
145145- PluginSignature::build("dbus get-all")
146146- .dbus_command()
147147- .accepts_dbus_client_options()
148148- .accepts_timeout()
149149- .usage("Get all D-Bus properties for the given object")
150150- .input_output_type(Type::Nothing, Type::Record(vec![]))
151151- .required_named("dest", SyntaxShape::String,
152152- "The name of the connection to read the property from",
153153- None)
154154- .required("object", SyntaxShape::String,
155155- "The path to the object to read the property from")
156156- .required("interface", SyntaxShape::String,
157157- "The name of the interface the property belongs to")
158158- .plugin_examples(vec![
159159- PluginExample {
160160- example: "dbus get-all --dest=org.mpris.MediaPlayer2.spotify \
161161- /org/mpris/MediaPlayer2 \
162162- org.mpris.MediaPlayer2.Player".into(),
163163- description: "Get the current player state of Spotify".into(),
164164- result: Some(Value::record(nu_protocol::record!(
165165- "CanPlay" => Value::bool(true, Span::unknown()),
166166- "Volume" => Value::float(0.43, Span::unknown()),
167167- "PlaybackStatus" => str!("Paused"),
168168- ), Span::unknown()))
169169- },
170170- ]),
171171- PluginSignature::build("dbus set")
172172- .dbus_command()
173173- .accepts_dbus_client_options()
174174- .accepts_timeout()
175175- .usage("Set a D-Bus property")
176176- .input_output_type(Type::Nothing, Type::Nothing)
177177- .named("signature", SyntaxShape::String,
178178- "Signature of the value to set, in D-Bus format.\n \
179179- If not provided, it will be determined from introspection.\n \
180180- If --no-introspect is specified and this is not provided, it will \
181181- be guessed (poorly)", None)
182182- .required_named("dest", SyntaxShape::String,
183183- "The name of the connection to write the property on",
184184- None)
185185- .required("object", SyntaxShape::String,
186186- "The path to the object to write the property on")
187187- .required("interface", SyntaxShape::String,
188188- "The name of the interface the property belongs to")
189189- .required("property", SyntaxShape::String,
190190- "The name of the property to write")
191191- .required("value", SyntaxShape::Any,
192192- "The value to write to the property")
193193- .plugin_examples(vec![
194194- PluginExample {
195195- example: "dbus set --dest=org.mpris.MediaPlayer2.spotify \
196196- /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player \
197197- Volume 0.5".into(),
198198- description: "Set the volume of Spotify to 50%".into(),
199199- result: None,
200200- },
201201- ]),
202202- PluginSignature::build("dbus list")
203203- .dbus_command()
204204- .accepts_dbus_client_options()
205205- .accepts_timeout()
206206- .usage("List all available connection names on the bus")
207207- .extra_usage("These can be used as arguments for --dest on any of the other commands.")
208208- .input_output_type(Type::Nothing, Type::List(Type::String.into()))
209209- .optional("pattern", SyntaxShape::String,
210210- "An optional glob-like pattern to filter the result by")
211211- .plugin_examples(vec![
212212- PluginExample {
213213- example: "dbus list".into(),
214214- description: "List all names available on the bus".into(),
215215- result: None,
216216- },
217217- PluginExample {
218218- example: "dbus list org.freedesktop.*".into(),
219219- description: "List top-level freedesktop.org names on the bus \
220220- (e.g. matches `org.freedesktop.PowerManagement`, \
221221- but not `org.freedesktop.Management.Inhibit`)".into(),
222222- result: Some(Value::list(vec![
223223- str!("org.freedesktop.DBus"),
224224- str!("org.freedesktop.Flatpak"),
225225- str!("org.freedesktop.Notifications"),
226226- ], Span::unknown())),
227227- },
228228- PluginExample {
229229- example: "dbus list org.mpris.MediaPlayer2.**".into(),
230230- description: "List all MPRIS2 media players on the bus".into(),
231231- result: Some(Value::list(vec![
232232- str!("org.mpris.MediaPlayer2.spotify"),
233233- str!("org.mpris.MediaPlayer2.kdeconnect.mpris_000001"),
234234- ], Span::unknown())),
235235- },
236236- ])
2222+ Box::new(commands::Main),
2323+ Box::new(commands::Introspect),
2424+ Box::new(commands::Call),
2525+ Box::new(commands::Get),
2626+ Box::new(commands::GetAll),
2727+ Box::new(commands::Set),
2828+ Box::new(commands::List),
23729 ]
23830 }
239239-240240- fn run(
241241- &self,
242242- name: &str,
243243- _engine: &EngineInterface,
244244- call: &EvaluatedCall,
245245- _input: &Value,
246246- ) -> Result<Value, LabeledError> {
247247- match name {
248248- "dbus" => Err(LabeledError {
249249- label: "The `dbus` command requires a subcommand".into(),
250250- msg: "add --help to see subcommands".into(),
251251- span: Some(call.head),
252252- }),
253253-254254- "dbus introspect" => self.introspect(call),
255255- "dbus call" => self.call(call),
256256- "dbus get" => self.get(call),
257257- "dbus get-all" => self.get_all(call),
258258- "dbus set" => self.set(call),
259259- "dbus list" => self.list(call),
260260-261261- _ => Err(LabeledError {
262262- label: "Plugin invoked with unknown command name".into(),
263263- msg: "unknown command".into(),
264264- span: Some(call.head),
265265- }),
266266- }
267267- }
26831}
2693227033/// For conveniently adding the base options to a dbus command
···27437 fn accepts_timeout(self) -> Self;
27538}
27639277277-impl DbusSignatureUtilExt for PluginSignature {
4040+impl DbusSignatureUtilExt for nu_protocol::Signature {
27841 fn dbus_command(self) -> Self {
279279- self.search_terms(vec!["dbus".into()])
280280- .category(nu_protocol::Category::Platform)
4242+ self.category(nu_protocol::Category::Platform)
28143 }
2824428345 fn accepts_dbus_client_options(self) -> Self {
···31274 )
31375 }
31476}
315315-316316-impl NuPluginDbus {
317317- fn introspect(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
318318- let config = DbusClientConfig::try_from(call)?;
319319- let dbus = DbusClient::new(config)?;
320320- let node = dbus.introspect(&call.get_flag("dest")?.unwrap(), &call.req(0)?)?;
321321- Ok(node.to_value(call.head))
322322- }
323323-324324- fn call(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
325325- let config = DbusClientConfig::try_from(call)?;
326326- let dbus = DbusClient::new(config)?;
327327- let values = dbus.call(
328328- &call.get_flag("dest")?.unwrap(),
329329- &call.req(0)?,
330330- &call.req(1)?,
331331- &call.req(2)?,
332332- call.get_flag("signature")?.as_ref(),
333333- &call.positional[3..],
334334- )?;
335335-336336- let flatten = !call.get_flag::<bool>("no-flatten")?.unwrap_or(false);
337337-338338- // Make the output easier to deal with by returning a list only if there are multiple return
339339- // values (not so common)
340340- match values.len() {
341341- 0 if flatten => Ok(Value::nothing(call.head)),
342342- 1 if flatten => Ok(values.into_iter().nth(0).unwrap()),
343343- _ => Ok(Value::list(values, call.head)),
344344- }
345345- }
346346-347347- fn get(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
348348- let config = DbusClientConfig::try_from(call)?;
349349- let dbus = DbusClient::new(config)?;
350350- dbus.get(
351351- &call.get_flag("dest")?.unwrap(),
352352- &call.req(0)?,
353353- &call.req(1)?,
354354- &call.req(2)?,
355355- )
356356- }
357357-358358- fn get_all(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
359359- let config = DbusClientConfig::try_from(call)?;
360360- let dbus = DbusClient::new(config)?;
361361- dbus.get_all(
362362- &call.get_flag("dest")?.unwrap(),
363363- &call.req(0)?,
364364- &call.req(1)?,
365365- )
366366- }
367367-368368- fn set(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
369369- let config = DbusClientConfig::try_from(call)?;
370370- let dbus = DbusClient::new(config)?;
371371- dbus.set(
372372- &call.get_flag("dest")?.unwrap(),
373373- &call.req(0)?,
374374- &call.req(1)?,
375375- &call.req(2)?,
376376- call.get_flag("signature")?.as_ref(),
377377- &call.req(3)?,
378378- )?;
379379- Ok(Value::nothing(call.head))
380380- }
381381-382382- fn list(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
383383- let config = DbusClientConfig::try_from(call)?;
384384- let dbus = DbusClient::new(config)?;
385385- let pattern = call
386386- .opt::<String>(0)?
387387- .map(|pat| Pattern::new(&pat, Some('.')));
388388- let result = dbus.list(pattern.as_ref())?;
389389- Ok(Value::list(
390390- result
391391- .into_iter()
392392- .map(|s| Value::string(s, call.head))
393393- .collect(),
394394- call.head,
395395- ))
396396- }
397397-}