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.
This commit is contained in:
2020-05-15 00:42:15 +01:00
parent 5eea970cf7
commit 41309d9f39
2 changed files with 357 additions and 40 deletions

View File

@@ -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<RwLock<Context>>,
@@ -35,6 +38,23 @@ pub struct Connection {
msgq: Arc<Mutex<VecDeque<dbus::Message>>>,
}
pub fn connection_interfaces() -> Vec<String> {
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<dyn telepathy::Connection + 'static> for std::rc::Rc<Connection> {
fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) {
&**self
}
}
impl Connection {
pub fn new(params: HashMap<&str, super::Variant>) -> Result<Self, dbus::tree::MethodErr> {
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<dyn telepathy::Connection + 'static> for std::rc::Rc<Connection> {
fn as_ref(&self) -> &(dyn telepathy::Connection + 'static) {
&**self
}
}
impl AsRef<dyn telepathy::ConnectionInterfaceContacts + 'static> for std::rc::Rc<Connection> {
fn as_ref(&self) -> &(dyn telepathy::ConnectionInterfaceContacts + 'static) {
&**self
}
}
impl AsRef<dyn telepathy::ConnectionInterfaceRequests + 'static> for std::rc::Rc<Connection> {
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<Vec<String>, 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<String, tree::MethodErr> {
@@ -368,7 +394,7 @@ impl telepathy::Connection for Connection {
fn get_self_handle(&self) -> Result<u32, tree::MethodErr> {
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<u32, tree::MethodErr> {
@@ -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<String, tree::MethodErr> {
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<bool, tree::MethodErr> {
@@ -471,6 +512,107 @@ impl telepathy::Connection for Connection {
}
}
impl AsRef<dyn telepathy::ConnectionInterfaceAvatars + 'static> for std::rc::Rc<Connection> {
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<String>, 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<u32>) -> Result<Vec<String>, tree::MethodErr> {
println!("Connection<{}>::get_avatar_tokens({:?})", self.id, contacts);
Ok(vec![])
}
fn get_known_avatar_tokens(
&self,
contacts: Vec<u32>,
) -> Result<::std::collections::HashMap<u32, String>, tree::MethodErr> {
println!(
"Connection<{}>::get_known_avatar_tokens({:?})",
self.id, contacts
);
Ok(HashMap::<u32, String>::new())
}
fn request_avatar(&self, contact: u32) -> Result<(Vec<u8>, String), tree::MethodErr> {
println!("Connection<{}>::request_avatar({})", self.id, contact);
Ok((vec![], "".to_string()))
}
fn request_avatars(&self, contacts: Vec<u32>) -> Result<(), tree::MethodErr> {
println!("Connection<{}>::request_avatar({:?})", self.id, contacts);
Ok(())
}
fn set_avatar(&self, _avatar: Vec<u8>, mimetype: &str) -> Result<String, tree::MethodErr> {
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<Vec<String>, tree::MethodErr> {
println!("Connection<{}>::supported_avatar_mimetypes()", self.id);
Ok(vec![])
}
fn minimum_avatar_height(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::minimum_avatar_height()", self.id);
Ok(0)
}
fn minimum_avatar_width(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::minimum_avatar_width()", self.id);
Ok(0)
}
fn recommended_avatar_height(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::recommended_avatar_height()", self.id);
Ok(0)
}
fn recommended_avatar_width(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::recommended_avatar_width()", self.id);
Ok(0)
}
fn maximum_avatar_height(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::maximum_avatar_height()", self.id);
Ok(0)
}
fn maximum_avatar_width(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::maximum_avatar_width()", self.id);
Ok(0)
}
fn maximum_avatar_bytes(&self) -> Result<u32, tree::MethodErr> {
println!("Connection<{}>::maximum_avatar_bytes()", self.id);
Ok(0)
}
}
impl AsRef<dyn telepathy::ConnectionInterfaceContacts + 'static> for std::rc::Rc<Connection> {
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::<String, super::Variant>::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<Vec<String>, 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<dyn telepathy::ConnectionInterfaceContactList + 'static> for std::rc::Rc<Connection> {
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<HashMap<u32, HashMap<String, super::Variant>>, 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::<String>,
) {
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<u32>,
message: &str,
) -> Result<(), tree::MethodErr> {
println!(
"Connection<{}>::request_subscription({:?}, {})",
self.id, contacts, message
);
Ok(())
}
fn authorize_publication(&self, contacts: Vec<u32>) -> Result<(), tree::MethodErr> {
println!(
"Connection<{}>::authorize_publication({:?})",
self.id, contacts
);
Ok(())
}
fn remove_contacts(&self, contacts: Vec<u32>) -> Result<(), tree::MethodErr> {
println!("Connection<{}>::remove_contacts({:?})", self.id, contacts);
Ok(())
}
fn unsubscribe(&self, contacts: Vec<u32>) -> Result<(), tree::MethodErr> {
println!("Connection<{}>::unsubscribe({:?})", self.id, contacts);
Ok(())
}
fn unpublish(&self, contacts: Vec<u32>) -> 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<u32, tree::MethodErr> {
println!("Connection<{}>::contact_list_state()", self.id);
Ok(3) // Success
}
fn contact_list_persists(&self) -> Result<bool, tree::MethodErr> {
println!("Connection<{}>::contact_list_persists()", self.id);
Ok(true)
}
fn can_change_contact_list(&self) -> Result<bool, tree::MethodErr> {
println!("Connection<{}>::can_change_contact_list()", self.id);
Ok(false)
}
fn request_uses_message(&self) -> Result<bool, tree::MethodErr> {
println!("Connection<{}>::request_uses_message()", self.id);
Ok(false)
}
fn download_at_connection(&self) -> Result<bool, tree::MethodErr> {
println!("Connection<{}>::download_at_connection()", self.id);
Ok(false)
}
}
impl AsRef<dyn telepathy::ConnectionInterfaceRequests + 'static> for std::rc::Rc<Connection> {
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<dyn telepathy::ConnectionInterfaceSimplePresence + 'static> for std::rc::Rc<Connection> {
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<u32>,
) -> Result<HashMap<u32, SimplePresenceSpec>, tree::MethodErr> {
println!("Connection<{}>::get_presences({:?})", self.id, contacts);
let mut out = HashMap::<u32, SimplePresenceSpec>::new();
for id in contacts {
out.insert(id, (2, "available".to_string(), "".to_string())); // Available
}
Ok(out)
}
fn statuses(&self) -> Result<HashMap<String, SimpleStatusSpec>, tree::MethodErr> {
println!("Connection<{}>::statuses()", self.id);
let mut out = HashMap::<String, SimpleStatusSpec>::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<u32, tree::MethodErr> {
println!("Connection<{}>::maximum_status_message_length()", self.id);
Ok(0)
}
}
fn escape_one(b: u8) -> String {
format!("_{:0<2x}", b)
}

View File

@@ -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(),