| //! Connection base / building block. |
| //! |
| //! Contains some helper structs and traits common to all Connection types.- |
| |
| use crate::{Message, to_c_str, c_str_to_slice, MessageType}; |
| use crate::message::MatchRule; |
| |
| #[cfg(not(feature = "native-channel"))] |
| mod ffichannel; |
| #[cfg(not(feature = "native-channel"))] |
| pub use ffichannel::Channel; |
| |
| #[cfg(feature = "native-channel")] |
| mod nativechannel; |
| #[cfg(feature = "native-channel")] |
| pub use nativechannel::Channel; |
| |
| |
| /// Which bus to connect to |
| #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] |
| pub enum BusType { |
| /// The Session bus - local to every logged in session |
| Session = ffi::DBusBusType::Session as isize, |
| /// The system wide bus |
| System = ffi::DBusBusType::System as isize, |
| /// The bus that started us, if any |
| Starter = ffi::DBusBusType::Starter as isize, |
| } |
| |
| /// Platform-specific file descriptor type |
| #[cfg(unix)] |
| pub type WatchFd = std::os::unix::io::RawFd; |
| |
| /// Platform-specific file descriptor type |
| #[cfg(windows)] |
| pub type WatchFd = std::os::windows::io::RawSocket; |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| /// A file descriptor, and an indication whether it should be read from, written to, or both. |
| pub struct Watch { |
| /// File descriptor |
| pub fd: WatchFd, |
| /// True if wakeup should happen when the file descriptor is ready for reading |
| pub read: bool, |
| /// True if wakeup should happen when the file descriptor is ready for writing |
| pub write: bool, |
| } |
| |
| /// Abstraction over different connections that send data |
| pub trait Sender { |
| /// Schedules a message for sending. |
| /// |
| /// Returns a serial number than can be used to match against a reply. |
| fn send(&self, msg: Message) -> Result<u32, ()>; |
| } |
| |
| /// Use in case you don't want the send the message, but just collect it instead. |
| impl Sender for std::cell::RefCell<Vec<Message>> { |
| fn send(&self, msg: Message) -> Result<u32, ()> { |
| self.borrow_mut().push(msg); |
| Ok(0) |
| } |
| } |
| |
| /// Use in case you don't want the send the message, but just collect it instead. |
| impl Sender for std::sync::Mutex<Vec<Message>> { |
| fn send(&self, msg: Message) -> Result<u32, ()> { |
| self.lock().unwrap().push(msg); |
| Ok(0) |
| } |
| } |
| |
| /// Token used to identify a callback in the MatchingReceiver trait |
| #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| pub struct Token(pub usize); |
| |
| /// Abstraction over different connections that receive data |
| pub trait MatchingReceiver { |
| /// Type of callback |
| type F; |
| /// Add a callback to be called in case a message matches. |
| /// |
| /// Returns an id that can be used to remove the callback. |
| fn start_receive(&self, m: MatchRule<'static>, f: Self::F) -> Token; |
| /// Remove a previously added callback. |
| fn stop_receive(&self, id: Token) -> Option<(MatchRule<'static>, Self::F)>; |
| } |
| |
| impl Sender for Channel { |
| fn send(&self, msg: Message) -> Result<u32, ()> { Channel::send(self, msg) } |
| } |
| |
| /// Handles what we need to be a good D-Bus citizen. |
| /// |
| /// Call this if you have not handled the message yourself: |
| /// * It handles calls to org.freedesktop.DBus.Peer. |
| /// * For other method calls, it sends an error reply back that the method was unknown. |
| pub fn default_reply(m: &Message) -> Option<Message> { |
| peer(&m).or_else(|| unknown_method(&m)) |
| } |
| |
| /// Replies if this is a call to org.freedesktop.DBus.Peer, otherwise returns None. |
| fn peer(m: &Message) -> Option<Message> { |
| if let Some(intf) = m.interface() { |
| if &*intf != "org.freedesktop.DBus.Peer" { return None; } |
| if let Some(method) = m.member() { |
| if &*method == "Ping" { return Some(m.method_return()) } |
| if &*method == "GetMachineId" { |
| let mut r = m.method_return(); |
| unsafe { |
| let id = ffi::dbus_get_local_machine_id(); |
| if !id.is_null() { |
| r = r.append1(c_str_to_slice(&(id as *const _)).unwrap()); |
| ffi::dbus_free(id as *mut _); |
| return Some(r) |
| } |
| } |
| return Some(m.error(&"org.freedesktop.DBus.Error.Failed".into(), &to_c_str("Failed to retreive UUID"))) |
| } |
| } |
| Some(m.error(&"org.freedesktop.DBus.Error.UnknownMethod".into(), &to_c_str("Method does not exist"))) |
| } else { None } |
| } |
| |
| /// For method calls, it replies that the method was unknown, otherwise returns None. |
| fn unknown_method(m: &Message) -> Option<Message> { |
| if m.msg_type() != MessageType::MethodCall { return None; } |
| // if m.get_no_reply() { return None; } // The reference implementation does not do this? |
| Some(m.error(&"org.freedesktop.DBus.Error.UnknownMethod".into(), &to_c_str("Path, Interface, or Method does not exist"))) |
| } |
| |
| #[test] |
| fn test_channel_send_sync() { |
| fn is_send<T: Send>(_: &T) {} |
| fn is_sync<T: Sync>(_: &T) {} |
| let c = Channel::get_private(BusType::Session).unwrap(); |
| is_send(&c); |
| is_sync(&c); |
| } |
| |
| #[test] |
| fn channel_simple_test() { |
| let mut c = Channel::get_private(BusType::Session).unwrap(); |
| assert!(c.is_connected()); |
| c.set_watch_enabled(true); |
| let fd = c.watch(); |
| println!("{:?}", fd); |
| let m = Message::new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames").unwrap(); |
| let reply = c.send(m).unwrap(); |
| let my_name = c.unique_name().unwrap(); |
| loop { |
| while let Some(mut msg) = c.pop_message() { |
| println!("{:?}", msg); |
| if msg.get_reply_serial() == Some(reply) { |
| let r = msg.as_result().unwrap(); |
| let z: crate::arg::Array<&str, _> = r.get1().unwrap(); |
| for n in z { |
| println!("{}", n); |
| if n == my_name { return; } // Hooray, we found ourselves! |
| } |
| assert!(false); |
| } else if let Some(r) = default_reply(&msg) { |
| c.send(r).unwrap(); |
| } |
| } |
| c.read_write(Some(std::time::Duration::from_millis(100))).unwrap(); |
| } |
| } |
| |
| #[test] |
| fn test_bus_type_is_compatible_with_set() { |
| use std::collections::HashSet; |
| |
| let mut set: HashSet<BusType> = HashSet::new(); |
| set.insert(BusType::Starter); |
| set.insert(BusType::Starter); |
| |
| assert_eq!(set.len(), 1); |
| assert!(!set.contains(&BusType::Session)); |
| assert!(!set.contains(&BusType::System)); |
| assert!(set.contains(&BusType::Starter)); |
| } |
| |
| |
| #[test] |
| fn watchmap() { |
| let mut c = Channel::get_private(BusType::Session).unwrap(); |
| c.set_watch_enabled(true); |
| let w = c.watch(); |
| assert_eq!(w.write, false); |
| assert_eq!(w.read, true); |
| c.set_watch_enabled(false); |
| println!("{:?}", w); |
| c.set_watch_enabled(true); |
| } |