From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/dbus/examples/adv_server.rs | 177 +++++++++++++++++++ third_party/rust/dbus/examples/argument_guide.md | 193 +++++++++++++++++++++ third_party/rust/dbus/examples/client.rs | 14 ++ third_party/rust/dbus/examples/properties.rs | 50 ++++++ .../rust/dbus/examples/properties_msgitem.rs | 10 ++ third_party/rust/dbus/examples/rtkit.rs | 67 +++++++ third_party/rust/dbus/examples/server.rs | 72 ++++++++ .../rust/dbus/examples/unity_focused_window.rs | 25 +++ 8 files changed, 608 insertions(+) create mode 100644 third_party/rust/dbus/examples/adv_server.rs create mode 100644 third_party/rust/dbus/examples/argument_guide.md create mode 100644 third_party/rust/dbus/examples/client.rs create mode 100644 third_party/rust/dbus/examples/properties.rs create mode 100644 third_party/rust/dbus/examples/properties_msgitem.rs create mode 100644 third_party/rust/dbus/examples/rtkit.rs create mode 100644 third_party/rust/dbus/examples/server.rs create mode 100644 third_party/rust/dbus/examples/unity_focused_window.rs (limited to 'third_party/rust/dbus/examples') diff --git a/third_party/rust/dbus/examples/adv_server.rs b/third_party/rust/dbus/examples/adv_server.rs new file mode 100644 index 0000000000..a71fc39a6a --- /dev/null +++ b/third_party/rust/dbus/examples/adv_server.rs @@ -0,0 +1,177 @@ +// More advanced server example. + +// This is supposed to look like a D-Bus service that allows the user to manipulate storage devices. + +// Note: in the dbus-codegen/example directory, there is a version of this example where dbus-codegen +// was used to create some boilerplate code - feel free to compare the two examples. + +extern crate dbus; + +use std::sync::Arc; +use std::sync::mpsc; +use std::cell::Cell; +use std::thread; + +use dbus::{Connection, BusType, tree, Path}; +use dbus::tree::{Interface, Signal, MTFn, Access, MethodErr, EmitsChangedSignal}; + +// Our storage device +#[derive(Debug)] +struct Device { + description: String, + path: Path<'static>, + index: i32, + online: Cell, + checking: Cell, +} + +// Every storage device has its own object path. +// We therefore create a link from the object path to the Device. +#[derive(Copy, Clone, Default, Debug)] +struct TData; +impl tree::DataType for TData { + type Tree = (); + type ObjectPath = Arc; + type Property = (); + type Interface = (); + type Method = (); + type Signal = (); +} + + +impl Device { + // Creates a "test" device (not a real one, since this is an example). + fn new_bogus(index: i32) -> Device { + Device { + description: format!("This is device {}, which is {}.", index, + ["totally awesome", "really fancy", "still going strong"][(index as usize) % 3]), + path: format!("/Device{}", index).into(), + index: index, + online: Cell::new(index % 2 == 0), + checking: Cell::new(false), + } + } +} + +// Here's where we implement the code for our interface. +fn create_iface(check_complete_s: mpsc::Sender) -> (Interface, TData>, Arc>) { + let f = tree::Factory::new_fn(); + + let check_complete = Arc::new(f.signal("CheckComplete", ())); + + (f.interface("com.example.dbus.rs.device", ()) + // The online property can be both set and get + .add_p(f.property::("online", ()) + .access(Access::ReadWrite) + .on_get(|i, m| { + let dev: &Arc = m.path.get_data(); + i.append(dev.online.get()); + Ok(()) + }) + .on_set(|i, m| { + let dev: &Arc = m.path.get_data(); + let b: bool = try!(i.read()); + if b && dev.checking.get() { + return Err(MethodErr::failed(&"Device currently under check, cannot bring online")) + } + dev.online.set(b); + Ok(()) + }) + ) + // The "checking" property is read only + .add_p(f.property::("checking", ()) + .emits_changed(EmitsChangedSignal::False) + .on_get(|i, m| { + let dev: &Arc = m.path.get_data(); + i.append(dev.checking.get()); + Ok(()) + }) + ) + // ...and so is the "description" property + .add_p(f.property::<&str,_>("description", ()) + .emits_changed(EmitsChangedSignal::Const) + .on_get(|i, m| { + let dev: &Arc = m.path.get_data(); + i.append(&dev.description); + Ok(()) + }) + ) + // ...add a method for starting a device check... + .add_m(f.method("check", (), move |m| { + let dev: &Arc = m.path.get_data(); + if dev.checking.get() { + return Err(MethodErr::failed(&"Device currently under check, cannot start another check")) + } + if dev.online.get() { + return Err(MethodErr::failed(&"Device is currently online, cannot start check")) + } + dev.checking.set(true); + + // Start some lengthy processing in a separate thread... + let devindex = dev.index; + let ch = check_complete_s.clone(); + thread::spawn(move || { + + // Bogus check of device + use std::time::Duration; + thread::sleep(Duration::from_secs(15)); + + // Tell main thread that we finished + ch.send(devindex).unwrap(); + }); + Ok(vec!(m.msg.method_return())) + })) + // Indicate that we send a special signal once checking has completed. + .add_s(check_complete.clone()) + , check_complete) +} + +fn create_tree(devices: &[Arc], iface: &Arc, TData>>) + -> tree::Tree, TData> { + + let f = tree::Factory::new_fn(); + let mut tree = f.tree(()); + for dev in devices { + tree = tree.add(f.object_path(dev.path.clone(), dev.clone()) + .introspectable() + .add(iface.clone()) + ); + } + tree +} + +fn run() -> Result<(), Box> { + // Create our bogus devices + let devices: Vec> = (0..10).map(|i| Arc::new(Device::new_bogus(i))).collect(); + + // Create tree + let (check_complete_s, check_complete_r) = mpsc::channel::(); + let (iface, sig) = create_iface(check_complete_s); + let tree = create_tree(&devices, &Arc::new(iface)); + + // Setup DBus connection + let c = try!(Connection::get_private(BusType::Session)); + try!(c.register_name("com.example.dbus.rs.advancedserverexample", 0)); + try!(tree.set_registered(&c, true)); + + // ...and serve incoming requests. + c.add_handler(tree); + loop { + // Wait for incoming messages. This will block up to one second. + // Discard the result - relevant messages have already been handled. + c.incoming(1000).next(); + + // Do all other things we need to do in our main loop. + if let Ok(idx) = check_complete_r.try_recv() { + let dev = &devices[idx as usize]; + dev.checking.set(false); + try!(c.send(sig.msg(&dev.path, &"com.example.dbus.rs.device".into())).map_err(|_| "Sending DBus signal failed")); + } + } +} + +fn main() { + if let Err(e) = run() { + println!("{}", e); + } +} diff --git a/third_party/rust/dbus/examples/argument_guide.md b/third_party/rust/dbus/examples/argument_guide.md new file mode 100644 index 0000000000..b36f2e937a --- /dev/null +++ b/third_party/rust/dbus/examples/argument_guide.md @@ -0,0 +1,193 @@ +Preamble +-------- + +The different ways you can append and get message arguments can be a bit bewildering. I've iterated a few times on the design and didn't want to lose backwards compatibility. + +This guide is to help you on your way. In addition, many of the examples in the examples directory append and read arguments. + +Code generation +--------------- + +First - if you can get D-Bus introspection data, you can use the the `dbus-codegen` tool to generate some boilerplate code for you. E g, if you want to talk to NetworkManager: + +```rust +cargo install dbus-codegen +dbus-codegen-rust -s -g -m None -d org.freedesktop.NetworkManager -p /org/freedesktop/NetworkManager > networkmanager.rs +``` + +You would then use this code like: + +```rust +// main.rs +mod networkmanager; + +/* ... */ + +// Start a connection to the system bus. +let c = Connection::get_private(BusType::System)?; + +// Make a "ConnPath" struct that just contains a Connection, a destination and a path. +let p = c.with_path("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", 5000); + +// Bring our generated code into scope. +use networkmanager::OrgFreedesktopNetworkManager; + +// Now we can call methods on our connpath from the "org.freedesktop.NetworkManager" interface. +let devices = c.get_all_devices()?; +``` + +There is also pre-generated code for standard D-Bus interfaces in the `stdintf` module. A similar example: + +```rust +let c = Connection::get_private(BusType::Session)?; + +// Make a "ConnPath" struct that just contains a Connection, a destination and a path. +let p = c.with_path("org.mpris.MediaPlayer2.rhythmbox", "/org/mpris/MediaPlayer2", 5000); + +// The ConnPath struct implements many traits, e g `org.freedesktop.DBus.Properties`. Bring the trait into scope. +use stdintf::org_freedesktop_dbus::Properties; + +// Now we can call org.freedesktop.DBus.Properties.Get just like an ordinary method and get the result back. +let metadata = p.get("org.mpris.MediaPlayer2.Player", "Metadata")?; +``` + +For more details, see `dbus-codegen-rust --help` and the `README.md` in the dbus-codegen directory. + +Now, if you want to make a service yourself, the generated code is more complex. And for some use cases, codegen isn't really an option, so let's move on: + +Append / get basic types +------------------------ + +If you just want to get/append simple types, just use `append1` / `append2` / `append3`, and +`read1` / `read2` / `read3`. The imaginary method below takes one byte parameter and one string parameter, and returns one string parameter and one int parameter. + +```rust +let m = Message::new_method_call(dest, path, intf, member)?.append2(5u8, "Foo"); +let r = c.send_with_reply_and_block(m, 2000)?; +let (data1, data2): (&str, i32) = c.read2()?; +``` + +Arrays and dictionaries +----------------------- + +D-Bus arrays and dictionaries usually correspond to `Vec` and `HashMap`. You can just append and get them like basic types: + +```rust +let v = vec![3i32, 4i32, 5i32]; +let mut map = HashMap::new(); +map.insert("Funghi", 5u16); +map.insert("Mold", 8u16); + +let m = Message::new_method_call(dest, path, intf, member)?.append2(v, map); +let r = c.send_with_reply_and_block(m, 2000)?; +let (data1, data2): (Vec, HashMap<&str, u16>) = r.read2()?; +``` + +Or combine them as you wish, e g, use a `Vec>`, a `HashMap>` or `HashMap>` to construct more difficult types. + +Slices can sometimes be used as arrays - e g, `&[&str]` can be appended, but only very simple types can be used with `get` and `read`, e g `&[u8]`. + +This is the easiest way to get started, but in case you want to avoid the overhead of creating `Vec` or `HashMap`s, the "Array and Dict types" and "Iter / IterAppend" sections offer useful alternatives. + +Variants +-------- + +Things are getting slightly more complex with Variants, because they are not strongly typed and thus not fit as well into Rust's strongly typed as arrays and dicts. + +If you know the type beforehand, it's still easy: + +```rust +let v = Variant("This is a variant containing a &str"); +let m = Message::new_method_call(dest, path, intf, member)?.append1(v); +let r = c.send_with_reply_and_block(m, 2000)?; +let z: Variant = r.read1()?; +println!("Method returned {}", z.0); +``` + +The `Variant` struct is just a wrapper with a public interior, so you can easily both read from it and write to it with the `.0` accessor. + +Sometimes you don't know the type beforehand. We can solve this in two ways (choose whichever is more appropriate for your use case), either through the trait object `Box` or through `Iter` / `IterAppend` (see later sections). + +Through trait objects: + +```rust +let x = Box::new(5000i32) as Box; +let m = Message::new_method_call(dest, path, intf, member)?.append1(Variant(x)); +let r = c.send_with_reply_and_block(m, 2000)?; +let z: Variant> = r.read1()?; +``` + +Ok, so we retrieved our `Box`. We now need to use the `RefArg` methods to probe it, to see what's inside. Easiest is to use `as_i64` or `as_str` if you want to test for integer or string types. Use `as_iter` if the variant contains a complex type you need to iterate over. +For floating point values, use `arg::cast` (this requires that the RefArg is `static` though, due to Rust type system limitations). +Match over `arg_type` if you need to know the exact type. + + +```rust +let z: Variant> = r.read1()?; +let value = &z.0; + +if let Some(s) = value.as_str() { println!("It's a string: {}", s); } +else if let Some(i) = value.as_i64() { println!("It's an integer: {}", i); } +else if let Some(f) = arg::cast::(value) { println!("It's a float: {}", f); } +else { println!("Don't know how to handle a {:?}", value.arg_type()) } +``` + +Dicts and variants are sometimes combined, e g, you might need to read a D-Bus dictionary of String to Variants. You can then read these as `HashMap>>`. + +Structs +------- + +D-Bus structs are implemented as Rust tuples. You can append and get tuples like you do with other types of arguments. + +TODO: Example + +Declare method arguments +------------------------ + +When you make a `Tree`, you want to declare what input and output arguments your method expects - so that correct D-Bus introspection data can be generated. You'll use the same types as you learned earlier in this guide: + +```rust +factory.method( /* ... */ ) +.inarg::>,_>("request") +.outarg::<&str,_>("reply") +``` + +The types are just for generating a correct signature, they are never instantiated. Many different types can generate the same signature - e g, `Array`, `Vec` and `&[u8]` will all generate the same signature. `Variant` will generate the same type signature regardless of what's inside, so just write `Variant<()>` for simplicity. + + +Iter / IterAppend +----------------- + +Iter and IterAppend are more low-level, direct methods to get and append arguments. They can, e g, come handy if you have more than five arguments to read. + +E g, for appending a variant with IterAppend you can use `IterAppend::new(&msg).append_variant(|i| i.append(5000i32))` to append what you need to your variant inside the closure. +To read a variant you can use `let i = msg.read1::>::()?` and then examine the methods on `i.0` to probe the variant. + +Array and Dict types +-------------------- + +These provide slightly better flexibility than using `Vec` and `HashMap` by instead integrating with `Iterator`. Here's an example where you can append and get a dictionary without having to create a HashMap: + +```rust +let x = &[("Hello", true), ("World", false)]; +let m = Message::new_method_call(dest, path, intf, member)?.append1(Dict::new(x)); +let r = c.send_with_reply_and_block(m, 2000)?; +let z: Dict = r.read1()?; +for (key, value) in z { /* do something */ } +``` + +An edge case where this is necessary is having floating point keys in a dictionary - this is supported in D-Bus but not in Rust's `HashMap`. I have never seen this in practice, though. + +Unusual types +------------- + +The types `Path`, `Signature` and `OwnedFd` are not often used, but they can be appended and read as other argument types. `Path` and `Signature` will return strings with a borrowed lifetime - use `.into_static()` if you want to untie that lifetime. + +For `OwnedFd`, which a wrapper around a file descriptor, remember that the file descriptor will be closed when it goes out of scope. + +MessageItem +----------- + +MessageItem was the first design - an enum representing a D-Bus argument. It still works, but I doubt you'll ever need to use it. Newer methods provide better type safety, speed, and ergonomics. + + diff --git a/third_party/rust/dbus/examples/client.rs b/third_party/rust/dbus/examples/client.rs new file mode 100644 index 0000000000..a8707013ed --- /dev/null +++ b/third_party/rust/dbus/examples/client.rs @@ -0,0 +1,14 @@ +extern crate dbus; + +use dbus::{Connection, BusType, Message}; +use dbus::arg::Array; + +fn main() { + let c = Connection::get_private(BusType::Session).unwrap(); + let m = Message::new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames").unwrap(); + let r = c.send_with_reply_and_block(m, 2000).unwrap(); + // ListNames returns one argument, which is an array of strings. + let arr: Array<&str, _> = r.get1().unwrap(); + for name in arr { println!("{}", name); } +} + diff --git a/third_party/rust/dbus/examples/properties.rs b/third_party/rust/dbus/examples/properties.rs new file mode 100644 index 0000000000..a410fc2ec1 --- /dev/null +++ b/third_party/rust/dbus/examples/properties.rs @@ -0,0 +1,50 @@ +extern crate dbus; + +use dbus::{Connection, BusType, stdintf, arg}; +use std::collections::HashMap; + +fn print_refarg(value: &arg::RefArg) { + // We don't know what type the value is. We'll try a few and fall back to + // debug printing if the value is more complex than that. + if let Some(s) = value.as_str() { println!("{}", s); } + else if let Some(i) = value.as_i64() { println!("{}", i); } + else { println!("{:?}", value); } +} + +fn main() { + // Connect to server and create a ConnPath. A ConnPath implements several interfaces, + // in this case we'll use OrgFreedesktopDBusProperties, which allows us to call "get". + let c = Connection::get_private(BusType::Session).unwrap(); + let p = c.with_path("org.mpris.MediaPlayer2.rhythmbox", "/org/mpris/MediaPlayer2", 5000); + use stdintf::org_freedesktop_dbus::Properties; + + // The Metadata property is a Dict. + + // Option 1: we can get the dict straight into a hashmap, like this: + + let metadata: HashMap>> = p.get("org.mpris.MediaPlayer2.Player", "Metadata").unwrap(); + + println!("Option 1:"); + + // We now iterate over the hashmap. + for (key, value) in metadata.iter() { + print!(" {}: ", key); + print_refarg(&value); + } + + + // Option 2: we can get the entire dict as a RefArg and get the values out by iterating over it. + + let metadata: Box = p.get("org.mpris.MediaPlayer2.Player", "Metadata").unwrap(); + + // When using "as_iter()" for a dict, we'll get one key, it's value, next key, it's value, etc. + let mut iter = metadata.as_iter().unwrap(); + + println!("Option 2:"); + while let Some(key) = iter.next() { + // Printing the key is easy, since we know it's a String. + print!(" {}: ", key.as_str().unwrap()); + let value = iter.next().unwrap(); + print_refarg(&value); + } +} diff --git a/third_party/rust/dbus/examples/properties_msgitem.rs b/third_party/rust/dbus/examples/properties_msgitem.rs new file mode 100644 index 0000000000..86e1aa5086 --- /dev/null +++ b/third_party/rust/dbus/examples/properties_msgitem.rs @@ -0,0 +1,10 @@ +extern crate dbus; + +use dbus::{Connection, BusType, Props}; + +fn main() { + let c = Connection::get_private(BusType::System).unwrap(); + let p = Props::new(&c, "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", 10000); + println!("BackendVersion: {:?}", p.get("BackendVersion").unwrap()) +} diff --git a/third_party/rust/dbus/examples/rtkit.rs b/third_party/rust/dbus/examples/rtkit.rs new file mode 100644 index 0000000000..2717d417b9 --- /dev/null +++ b/third_party/rust/dbus/examples/rtkit.rs @@ -0,0 +1,67 @@ +/* This example asks the rtkit service to make our thread realtime priority. + Rtkit puts a few limitations on us to let us become realtime, such as setting + RLIMIT_RTTIME correctly, hence the syscalls. */ + +extern crate dbus; +extern crate libc; + +use std::cmp; + +use dbus::{Connection, BusType, Props, MessageItem, Message}; + +fn item_as_i64(i: MessageItem) -> Result> { + match i { + MessageItem::Int32(i) => Ok(i as i64), + MessageItem::Int64(i) => Ok(i), + _ => Err(Box::from(&*format!("Property is not integer ({:?})", i))) + } +} + +fn rtkit_set_realtime(c: &Connection, thread: u64, prio: u32) -> Result<(), ::dbus::Error> { + let mut m = Message::new_method_call("org.freedesktop.RealtimeKit1", "/org/freedesktop/RealtimeKit1", + "org.freedesktop.RealtimeKit1", "MakeThreadRealtime").unwrap(); + m.append_items(&[thread.into(), prio.into()]); + let mut r = try!(c.send_with_reply_and_block(m, 10000)); + r.as_result().map(|_| ()) +} + +fn make_realtime(prio: u32) -> Result> { + let c = try!(Connection::get_private(BusType::System)); + + let p = Props::new(&c, "org.freedesktop.RealtimeKit1", "/org/freedesktop/RealtimeKit1", + "org.freedesktop.RealtimeKit1", 10000); + + // Make sure we don't fail by wanting too much + let max_prio = try!(item_as_i64(try!(p.get("MaxRealtimePriority")))) as u32; + let prio = cmp::min(prio, max_prio); + + // Enforce RLIMIT_RTPRIO, also a must before asking rtkit for rtprio + let max_rttime = try!(item_as_i64(try!(p.get("RTTimeUSecMax")))) as u64; + let new_limit = libc::rlimit64 { rlim_cur: max_rttime, rlim_max: max_rttime }; + let mut old_limit = new_limit; + if unsafe { libc::getrlimit64(libc::RLIMIT_RTTIME, &mut old_limit) } < 0 { + return Err(Box::from("getrlimit failed")); + } + if unsafe { libc::setrlimit64(libc::RLIMIT_RTTIME, &new_limit) } < 0 { + return Err(Box::from("setrlimit failed")); + } + + // Finally, let's ask rtkit to make us realtime + let thread_id = unsafe { libc::syscall(libc::SYS_gettid) }; + let r = rtkit_set_realtime(&c, thread_id as u64, prio); + + if r.is_err() { + unsafe { libc::setrlimit64(libc::RLIMIT_RTTIME, &old_limit) }; + } + + try!(r); + Ok(prio) +} + + +fn main() { + match make_realtime(5) { + Ok(n) => println!("Got rtprio, level {}", n), + Err(e) => println!("No rtprio: {}", e), + } +} diff --git a/third_party/rust/dbus/examples/server.rs b/third_party/rust/dbus/examples/server.rs new file mode 100644 index 0000000000..105593fccc --- /dev/null +++ b/third_party/rust/dbus/examples/server.rs @@ -0,0 +1,72 @@ +/* This example creates a D-Bus server with the following functionality: + It registers the "com.example.dbustest" name, creates a "/hello" object path, + which has an "com.example.dbustest" interface. + + The interface has a "Hello" method (which takes no arguments and returns a string), + and a "HelloHappened" signal (with a string argument) which is sent every time + someone calls the "Hello" method. +*/ + + +extern crate dbus; + +use std::sync::Arc; +use dbus::{Connection, BusType, NameFlag}; +use dbus::tree::Factory; + +fn main() { + // Let's start by starting up a connection to the session bus and register a name. + let c = Connection::get_private(BusType::Session).unwrap(); + c.register_name("com.example.dbustest", NameFlag::ReplaceExisting as u32).unwrap(); + + // The choice of factory tells us what type of tree we want, + // and if we want any extra data inside. We pick the simplest variant. + let f = Factory::new_fn::<()>(); + + // We create the signal first, since we'll need it in both inside the method callback + // and when creating the tree. + let signal = Arc::new(f.signal("HelloHappened", ()).sarg::<&str,_>("sender")); + let signal2 = signal.clone(); + + // We create a tree with one object path inside and make that path introspectable. + let tree = f.tree(()).add(f.object_path("/hello", ()).introspectable().add( + + // We add an interface to the object path... + f.interface("com.example.dbustest", ()).add_m( + + // ...and a method inside the interface. + f.method("Hello", (), move |m| { + + // This is the callback that will be called when another peer on the bus calls our method. + // the callback receives "MethodInfo" struct and can return either an error, or a list of + // messages to send back. + + let name: &str = m.msg.read1()?; + let s = format!("Hello {}!", name); + let mret = m.msg.method_return().append1(s); + + let sig = signal.msg(m.path.get_name(), m.iface.get_name()) + .append1(&*name); + + // Two messages will be returned - one is the method return (and should always be there), + // and in our case we also have a signal we want to send at the same time. + Ok(vec!(mret, sig)) + + // Our method has one output argument and one input argument. + }).outarg::<&str,_>("reply") + .inarg::<&str,_>("name") + + // We also add the signal to the interface. This is mainly for introspection. + ).add_s(signal2) + )); + + // We register all object paths in the tree. + tree.set_registered(&c, true).unwrap(); + + // We add the tree to the connection so that incoming method calls will be handled + // automatically during calls to "incoming". + c.add_handler(tree); + + // Serve other peers forever. + loop { c.incoming(1000).next(); } +} diff --git a/third_party/rust/dbus/examples/unity_focused_window.rs b/third_party/rust/dbus/examples/unity_focused_window.rs new file mode 100644 index 0000000000..a0e07d0372 --- /dev/null +++ b/third_party/rust/dbus/examples/unity_focused_window.rs @@ -0,0 +1,25 @@ +extern crate dbus; + +// Tracks currently focused window under the Unity desktop by listening to the +// FocusedWindowChanged signal. The signal contains "window_id", "app_id" and "stage", +// we print only "app_id". + +use dbus::{Connection, BusType, ConnectionItem}; + +fn focus_msg(ci: &ConnectionItem) -> Option<&str> { + let m = if let &ConnectionItem::Signal(ref s) = ci { s } else { return None }; + if &*m.interface().unwrap() != "com.canonical.Unity.WindowStack" { return None }; + if &*m.member().unwrap() != "FocusedWindowChanged" { return None }; + let (_, app) = m.get2::(); + app +} + +fn main() { + let c = Connection::get_private(BusType::Session).unwrap(); + c.add_match("interface='com.canonical.Unity.WindowStack',member='FocusedWindowChanged'").unwrap(); + + for i in c.iter(1000) { + if let Some(app) = focus_msg(&i) { println!("{} has now focus.", app) }; + } +} + -- cgit v1.2.3