From 41309d9f393964cf4926b29608d54a002885481b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 15 May 2020 00:42:15 +0100 Subject: [PATCH] Look online to Empathy It seems Empathy requires the ContactLists and Presence interfaces if it is to use your connection and show it as online. In addition, we were emitting the "connected" signal from the wrong source - it needed to have the object path of the specific connection, rather than the general connection object path. So now we can show as connected in Empathy, but we can't add a contact yet - presumably that's another interface. Sending a message to a connected deltachat account does nothing yet, as the message from deltachat-core is unhandled. --- src/padfoot/connection.rs | 392 +++++++++++++++++++++++++++--- src/padfoot/connection_manager.rs | 5 +- 2 files changed, 357 insertions(+), 40 deletions(-) diff --git a/src/padfoot/connection.rs b/src/padfoot/connection.rs index cbff5b2..f52fabd 100644 --- a/src/padfoot/connection.rs +++ b/src/padfoot/connection.rs @@ -1,4 +1,5 @@ use crate::telepathy; +use crate::telepathy::ConnectionInterfaceContacts; use dbus::blocking::{stdintf::org_freedesktop_dbus::RequestNameReply, LocalConnection}; use dbus::channel::Sender; use dbus::message::SignalArgs; @@ -10,6 +11,7 @@ use dc::Event; use deltachat as dc; use std::collections::{HashMap, VecDeque}; +use std::convert::TryInto; use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::Duration; @@ -25,6 +27,7 @@ enum ConnState { } #[derive(Debug)] +// A Deltachast connection uses email addresses as handles, and delta's Db IDs pub struct Connection { id: String, ctx: Arc>, @@ -35,6 +38,23 @@ pub struct Connection { msgq: Arc>>, } +pub fn connection_interfaces() -> Vec { + vec![ + "org.freedesktop.Telepathy.Connection".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.SimplePresence".to_string(), + ] +} + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) { + &**self + } +} + impl Connection { pub fn new(params: HashMap<&str, super::Variant>) -> Result { let err = Err(dbus::tree::MethodErr::no_arg()); @@ -151,19 +171,34 @@ impl Connection { let conn_iface = telepathy::connection_server(&f, (), move |_| c_rc1.clone()); let c_rc2 = c_rc.clone(); - let contacts_iface = - telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc2.clone()); + let avatars_iface = + telepathy::connection_interface_avatars_server(&f, (), move |_| c_rc2.clone()); let c_rc3 = c_rc.clone(); + let contacts_iface = + telepathy::connection_interface_contacts_server(&f, (), move |_| c_rc3.clone()); + + let c_rc4 = c_rc.clone(); + let contact_list_iface = + telepathy::connection_interface_contact_list_server(&f, (), move |_| c_rc4.clone()); + + let c_rc5 = c_rc.clone(); let requests_iface = - telepathy::connection_interface_requests_server(&f, (), move |_| c_rc3.clone()); + telepathy::connection_interface_requests_server(&f, (), move |_| c_rc5.clone()); + + let c_rc6 = c_rc.clone(); + let simple_presence_iface = + telepathy::connection_interface_simple_presence_server(&f, (), move |_| c_rc6.clone()); tree = tree.add( f.object_path(path.clone(), ()) .introspectable() .add(conn_iface) + .add(avatars_iface) .add(contacts_iface) - .add(requests_iface), + .add(contact_list_iface) + .add(requests_iface) + .add(simple_presence_iface), ); tree = tree.add(f.object_path("/", ()).introspectable()); @@ -229,24 +264,6 @@ impl Connection { } } -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) { - &**self - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) { - &**self - } -} - -impl AsRef for std::rc::Rc { - fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) { - &**self - } -} - impl telepathy::Connection for Connection { // In connect(), we start the threads that drive the deltachat context fn connect(&self) -> Result<(), tree::MethodErr> { @@ -254,6 +271,7 @@ impl telepathy::Connection for Connection { let inbox_ctx = self.ctx.clone(); let state = self.state.clone(); + let id = self.id.clone(); let _inbox_thread = thread::spawn(move || { while *state.read().unwrap() != ConnState::Disconnected { dc::job::perform_inbox_jobs(&inbox_ctx.read().unwrap()); @@ -265,10 +283,13 @@ impl telepathy::Connection for Connection { } } } + + println!("Connection<{}>::connect(): inbox thread exited", id); }); let smtp_ctx = self.ctx.clone(); let state = self.state.clone(); + let id = self.id.clone(); let _smtp_thread = thread::spawn(move || { while *state.read().unwrap() != ConnState::Disconnected { dc::job::perform_smtp_jobs(&smtp_ctx.read().unwrap()); @@ -276,10 +297,13 @@ impl telepathy::Connection for Connection { dc::job::perform_smtp_idle(&smtp_ctx.read().unwrap()); } } + + println!("Connection<{}>::connect(): smtp thread exited", id); }); let mvbox_ctx = self.ctx.clone(); let state = self.state.clone(); + let id = self.id.clone(); let _mvbox_thread = thread::spawn(move || { while *state.read().unwrap() != ConnState::Disconnected { dc::job::perform_mvbox_fetch(&mvbox_ctx.read().unwrap()); @@ -287,10 +311,13 @@ impl telepathy::Connection for Connection { dc::job::perform_mvbox_idle(&mvbox_ctx.read().unwrap()); } } + + println!("Connection<{}>::connect(): mvbox thread exited", id); }); let sentbox_ctx = self.ctx.clone(); let state = self.state.clone(); + let id = self.id.clone(); let _sentbox_thread = thread::spawn(move || { while *state.read().unwrap() != ConnState::Disconnected { dc::job::perform_sentbox_fetch(&sentbox_ctx.read().unwrap()); @@ -298,6 +325,8 @@ impl telepathy::Connection for Connection { dc::job::perform_sentbox_idle(&sentbox_ctx.read().unwrap()); } } + + println!("Connection<{}>::connect(): sentbox thread exited", id); }); // Just pretend to be connected all the time for now. Tracking IMAP+SMTP @@ -306,14 +335,14 @@ impl telepathy::Connection for Connection { let mut w = state.write().unwrap(); *w = ConnState::Connected; - // Emit a NewConnection signal for the benefit of others, but the caller + // Emit a StatusChanged signal for the benefit of others, but the caller // learns from our RPC response let sig = telepathy::ConnectionStatusChanged { status: 0, // Connected reason: 1, // Requested }; - let dbus_conn_path = dbus::strings::Path::new(CONN_OBJECT_PATH.to_string()) + let dbus_conn_path = dbus::strings::Path::new(self.path().to_string()) .expect("Object path should meet DBUS requirements"); self.msgq @@ -347,10 +376,7 @@ impl telepathy::Connection for Connection { fn get_interfaces(&self) -> Result, tree::MethodErr> { println!("Connection<{}>::get_interfaces()", self.id); - Ok(vec![ - "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), - ]) + Ok(connection_interfaces()) } fn get_protocol(&self) -> Result { @@ -368,7 +394,7 @@ impl telepathy::Connection for Connection { fn get_self_handle(&self) -> Result { println!("Connection<{}>::get_self_handle()", self.id); - Ok(deltachat::constants::DC_CONTACT_ID_SELF) + Ok(dc::constants::DC_CONTACT_ID_SELF) } fn status(&self) -> Result { @@ -389,7 +415,9 @@ impl telepathy::Connection for Connection { "Connection<{}>::hold_handles({}, {:?})", self.id, handle_type, handles ); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? + + // Since HasImmortalHandles is true, this doesn't need to do anything + Ok(()) } fn inspect_handles( @@ -416,7 +444,9 @@ impl telepathy::Connection for Connection { "Connection<{}>::release_handles({}, {:?})", self.id, handle_type, handles ); - Err(tree::MethodErr::no_arg()) // FIXME: should be NotImplemented? + + // Since HasImmortalHandles is true, we don't need to do anything + Ok(()) } fn request_channel( @@ -461,7 +491,18 @@ impl telepathy::Connection for Connection { fn self_id(&self) -> Result { println!("Connection<{}>::self_id()", self.id); - Ok("Yourself".to_string()) // FIXME: this could be passed through config + let contact = match dc::contact::Contact::get_by_id( + &self.ctx.read().unwrap(), + dc::constants::DC_CONTACT_ID_SELF, + ) { + Ok(c) => c, + Err(e) => { + println!(" err: {}", e); + return Err(tree::MethodErr::no_arg()); + } + }; + + Ok(contact.get_addr().to_string()) } fn has_immortal_handles(&self) -> Result { @@ -471,6 +512,107 @@ impl telepathy::Connection for Connection { } } +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceAvatars + 'static) { + &**self + } +} + +// TODO: come back and do this properly, I'm just putting it in for consistency +impl telepathy::ConnectionInterfaceAvatars for Connection { + fn get_avatar_requirements( + &self, + ) -> Result<(Vec, u16, u16, u16, u16, u32), tree::MethodErr> { + println!("Connection<{}>::get_avatar_requirements()", self.id); + Ok((vec![], 0, 0, 0, 0, 0)) + } + + fn get_avatar_tokens(&self, contacts: Vec) -> Result, tree::MethodErr> { + println!("Connection<{}>::get_avatar_tokens({:?})", self.id, contacts); + Ok(vec![]) + } + + fn get_known_avatar_tokens( + &self, + contacts: Vec, + ) -> Result<::std::collections::HashMap, tree::MethodErr> { + println!( + "Connection<{}>::get_known_avatar_tokens({:?})", + self.id, contacts + ); + Ok(HashMap::::new()) + } + + fn request_avatar(&self, contact: u32) -> Result<(Vec, String), tree::MethodErr> { + println!("Connection<{}>::request_avatar({})", self.id, contact); + Ok((vec![], "".to_string())) + } + + fn request_avatars(&self, contacts: Vec) -> Result<(), tree::MethodErr> { + println!("Connection<{}>::request_avatar({:?})", self.id, contacts); + Ok(()) + } + + fn set_avatar(&self, _avatar: Vec, mimetype: &str) -> Result { + println!( + "Connection<{}>::set_avatar((data), {:?})", + self.id, mimetype + ); + Ok("".to_string()) + } + + fn clear_avatar(&self) -> Result<(), tree::MethodErr> { + println!("Connection<{}>::clear_avatar()", self.id); + Ok(()) + } + + fn supported_avatar_mimetypes(&self) -> Result, tree::MethodErr> { + println!("Connection<{}>::supported_avatar_mimetypes()", self.id); + Ok(vec![]) + } + + fn minimum_avatar_height(&self) -> Result { + println!("Connection<{}>::minimum_avatar_height()", self.id); + Ok(0) + } + + fn minimum_avatar_width(&self) -> Result { + println!("Connection<{}>::minimum_avatar_width()", self.id); + Ok(0) + } + + fn recommended_avatar_height(&self) -> Result { + println!("Connection<{}>::recommended_avatar_height()", self.id); + Ok(0) + } + + fn recommended_avatar_width(&self) -> Result { + println!("Connection<{}>::recommended_avatar_width()", self.id); + Ok(0) + } + + fn maximum_avatar_height(&self) -> Result { + println!("Connection<{}>::maximum_avatar_height()", self.id); + Ok(0) + } + + fn maximum_avatar_width(&self) -> Result { + println!("Connection<{}>::maximum_avatar_width()", self.id); + Ok(0) + } + + fn maximum_avatar_bytes(&self) -> Result { + println!("Connection<{}>::maximum_avatar_bytes()", self.id); + Ok(0) + } +} + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) { + &**self + } +} + impl telepathy::ConnectionInterfaceContacts for Connection { fn get_contact_attributes( &self, @@ -488,23 +630,33 @@ impl telepathy::ConnectionInterfaceContacts for Connection { // FIXME: work out how to use get_all let contact = match dc::contact::Contact::get_by_id(&self.ctx.read().unwrap(), *id) { Ok(c) => c, - Err(_e) => continue, // FIXME: Ignore the error. Whatevs. + Err(_e) => continue, // Invalid IDs are silently ignored }; let mut props = HashMap::::new(); // This is mandatory props.insert( "org.freedesktop.Telepathy.Connection/contact-id".to_string(), - arg::Variant(Box::new(contact.get_display_name().to_string())), + arg::Variant(Box::new(contact.get_addr().to_string())), ); - // We don't advertise this interface but empathy is being weird. // The empty string means "no avatar" props.insert( "org.freedesktop.Telepathy.Connection.Interface.Avatars/token".to_string(), arg::Variant(Box::new("".to_string())), ); + // TODO: we need to publish DBUS services on these endpoints + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.ContactList/publish".to_string(), + arg::Variant(Box::new(4)), + ); + + props.insert( + "org.freedesktop.Telepathy.Connection.Interface.ContactList/subscribe".to_string(), + arg::Variant(Box::new(4)), + ); + out.insert(*id, props); } @@ -532,7 +684,113 @@ impl telepathy::ConnectionInterfaceContacts for Connection { fn contact_attribute_interfaces(&self) -> Result, tree::MethodErr> { println!("Connection<{}>::contact_attribute_interfaces()", self.id); - Ok(vec![]) + Ok(vec![ + "org.freedesktop.Telepathy.Connection".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.Avatars".to_string(), + "org.freedesktop.Telepathy.Connection.Interface.ContactList".to_string(), + ]) + } +} + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContactList + 'static) { + &**self + } +} + +// FIXME: come back and do this properly later +impl telepathy::ConnectionInterfaceContactList for Connection { + fn get_contact_list_attributes( + &self, + interfaces: Vec<&str>, + hold: bool, + ) -> Result>, tree::MethodErr> { + println!( + "Connection<{}>::get_contact_list_attributes({:?}, {})", + self.id, interfaces, hold + ); + + let ids = match dc::contact::Contact::get_all( + &self.ctx.read().unwrap(), + dc::constants::DC_GCL_ADD_SELF.try_into().unwrap(), + None::, + ) { + Ok(c) => c, + Err(e) => { + println!(" err: {}", e); + return Err(tree::MethodErr::no_arg()); + } + }; + + self.get_contact_attributes(ids, vec![], true) + } + + fn request_subscription( + &self, + contacts: Vec, + message: &str, + ) -> Result<(), tree::MethodErr> { + println!( + "Connection<{}>::request_subscription({:?}, {})", + self.id, contacts, message + ); + Ok(()) + } + + fn authorize_publication(&self, contacts: Vec) -> Result<(), tree::MethodErr> { + println!( + "Connection<{}>::authorize_publication({:?})", + self.id, contacts + ); + Ok(()) + } + fn remove_contacts(&self, contacts: Vec) -> Result<(), tree::MethodErr> { + println!("Connection<{}>::remove_contacts({:?})", self.id, contacts); + Ok(()) + } + fn unsubscribe(&self, contacts: Vec) -> Result<(), tree::MethodErr> { + println!("Connection<{}>::unsubscribe({:?})", self.id, contacts); + Ok(()) + } + + fn unpublish(&self, contacts: Vec) -> Result<(), tree::MethodErr> { + println!("Connection<{}>::unpublish({:?})", self.id, contacts); + Ok(()) + } + + fn download(&self) -> Result<(), tree::MethodErr> { + println!("Connection<{}>::download()", self.id); + Ok(()) + } + + fn contact_list_state(&self) -> Result { + println!("Connection<{}>::contact_list_state()", self.id); + Ok(3) // Success + } + + fn contact_list_persists(&self) -> Result { + println!("Connection<{}>::contact_list_persists()", self.id); + Ok(true) + } + + fn can_change_contact_list(&self) -> Result { + println!("Connection<{}>::can_change_contact_list()", self.id); + Ok(false) + } + fn request_uses_message(&self) -> Result { + println!("Connection<{}>::request_uses_message()", self.id); + Ok(false) + } + + fn download_at_connection(&self) -> Result { + println!("Connection<{}>::download_at_connection()", self.id); + Ok(false) + } +} + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceRequests + 'static) { + &**self } } @@ -593,6 +851,68 @@ impl telepathy::ConnectionInterfaceRequests for Connection { } } +pub type SimplePresenceSpec = ( + u32, // connection presence type + String, // status + String, // status message +); + +pub type SimpleStatusSpec = ( + u32, // connection presence type + bool, // may set on self? + bool, // can have message? +); + +impl AsRef for std::rc::Rc { + fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceSimplePresence + 'static) { + &**self + } +} + +impl telepathy::ConnectionInterfaceSimplePresence for Connection { + fn set_presence(&self, status: &str, status_message: &str) -> Result<(), tree::MethodErr> { + println!( + "Connection<{}>::set_presence({}, {:?})", + self.id, status, status_message + ); + + // FIXME: emit a presence changed signal + + Ok(()) + } + + fn get_presences( + &self, + contacts: Vec, + ) -> Result, tree::MethodErr> { + println!("Connection<{}>::get_presences({:?})", self.id, contacts); + + let mut out = HashMap::::new(); + + for id in contacts { + out.insert(id, (2, "available".to_string(), "".to_string())); // Available + } + + Ok(out) + } + + fn statuses(&self) -> Result, tree::MethodErr> { + println!("Connection<{}>::statuses()", self.id); + + let mut out = HashMap::::new(); + + out.insert("available".to_string(), (2, true, false)); + out.insert("offline".to_string(), (1, true, false)); + + Ok(out) + } + + fn maximum_status_message_length(&self) -> Result { + println!("Connection<{}>::maximum_status_message_length()", self.id); + Ok(0) + } +} + fn escape_one(b: u8) -> String { format!("_{:0<2x}", b) } diff --git a/src/padfoot/connection_manager.rs b/src/padfoot/connection_manager.rs index 616f441..333eb6b 100644 --- a/src/padfoot/connection_manager.rs +++ b/src/padfoot/connection_manager.rs @@ -129,10 +129,7 @@ impl telepathy::ConnectionManager for ConnectionManager { props.insert( "org.freedesktop.Telepathy.Protocol.ConnectionInterfaces".to_string(), - arg::Variant(Box::new(vec![ - "org.freedesktop.Telepathy.Connection.Interface.Contacts".to_string(), - "org.freedesktop.Telepathy.Connection.Interface.Requests".to_string(), - ])), + arg::Variant(Box::new(super::connection_interfaces())), ); props.insert( "org.freedesktop.Telepathy.Protocol.EnglishName".to_string(),