/* * connection.c - HazeConnection source * Copyright (C) 2007 Will Thompson * Copyright (C) 2007-2009 Collabora Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "debug.h" #include "defines.h" #include "connection-manager.h" #include "connection.h" #include "connection-presence.h" #include "connection-aliasing.h" #include "connection-avatars.h" #include "connection-mail.h" #include "extensions/extensions.h" #include "request.h" #include "connection-capabilities.h" #ifdef HAVE_LIBINTL_H # include #else # define dgettext(domain, msgid) (msgid) #endif enum { PROP_PARAMETERS = 1, PROP_USERNAME, PROP_PASSWORD, PROP_PRPL_ID, PROP_PRPL_INFO, LAST_PROPERTY } HazeConnectionProperties; G_DEFINE_TYPE_WITH_CODE(HazeConnection, haze_connection, TP_TYPE_BASE_CONNECTION, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, tp_dbus_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, tp_presence_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, tp_presence_mixin_simple_presence_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING, haze_connection_aliasing_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS, haze_connection_avatars_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, haze_connection_contact_capabilities_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CAPABILITIES, haze_connection_capabilities_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS, tp_contacts_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST, tp_base_contact_list_mixin_list_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS, tp_base_contact_list_mixin_groups_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING, tp_base_contact_list_mixin_blocking_iface_init); G_IMPLEMENT_INTERFACE (HAZE_TYPE_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION, haze_connection_mail_iface_init); ); static const gchar * implemented_interfaces[] = { /* Conditionally present */ TP_IFACE_CONNECTION_INTERFACE_AVATARS, HAZE_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING, # define HAZE_NUM_CONDITIONAL_INTERFACES 3 /* Always present */ TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST, TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS, TP_IFACE_CONNECTION_INTERFACE_REQUESTS, TP_IFACE_CONNECTION_INTERFACE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_CAPABILITIES, TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, TP_IFACE_CONNECTION_INTERFACE_CONTACTS, /* TODO: This is a lie. Not all protocols supported by libpurple * actually have the concept of a user-settable alias, but * there's no way for the UI to know (yet). */ TP_IFACE_CONNECTION_INTERFACE_ALIASING, NULL }; const gchar ** haze_connection_get_implemented_interfaces (void) { return implemented_interfaces; } const gchar ** haze_connection_get_guaranteed_interfaces (void) { return implemented_interfaces + HAZE_NUM_CONDITIONAL_INTERFACES; } struct _HazeConnectionPrivate { gchar *username; gchar *password; GHashTable *parameters; gchar *prpl_id; PurplePluginProtocolInfo *prpl_info; /* Set if purple_account_request_password() was called */ gpointer password_request; /* Set if purple_account_disconnect has been called or is scheduled to be * called, so should not be called again. */ gboolean disconnecting; /* Set to TRUE when purple_account_connect has been called. */ gboolean connect_called; gboolean dispose_has_run; }; #define PC_GET_BASE_CONN(pc) \ (ACCOUNT_GET_TP_BASE_CONNECTION (purple_connection_get_account (pc))) static void connected_cb (PurpleConnection *pc) { TpBaseConnection *base_conn = PC_GET_BASE_CONN (pc); HazeConnection *conn = HAZE_CONNECTION (base_conn); PurplePluginProtocolInfo *prpl_info = HAZE_CONNECTION_GET_PRPL_INFO (conn); if (prpl_info->icon_spec.format != NULL) { static const gchar *avatar_ifaces[] = { TP_IFACE_CONNECTION_INTERFACE_AVATARS, NULL }; tp_base_connection_add_interfaces (base_conn, avatar_ifaces); } if (prpl_info->add_deny != NULL) { static const gchar *blocking_ifaces[] = { TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING, NULL }; tp_base_connection_add_interfaces (base_conn, blocking_ifaces); } tp_base_contact_list_set_list_received ( (TpBaseContactList *) conn->contact_list); tp_base_connection_change_status (base_conn, TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); } static void map_purple_error_to_tp ( PurpleConnectionError purple_reason, gboolean while_connecting, TpConnectionStatusReason *tp_reason, const gchar **tp_error_name) { g_assert (tp_reason != NULL); g_assert (tp_error_name != NULL); #define set_both(suffix) \ G_STMT_START { \ *tp_reason = TP_CONNECTION_STATUS_REASON_ ## suffix; \ *tp_error_name = TP_ERROR_STR_ ## suffix; \ } G_STMT_END #define trivial_case(suffix) \ case PURPLE_CONNECTION_ERROR_ ## suffix: \ set_both (suffix); \ break; switch (purple_reason) { case PURPLE_CONNECTION_ERROR_NETWORK_ERROR: if (while_connecting) *tp_error_name = TP_ERROR_STR_CONNECTION_FAILED; else *tp_error_name = TP_ERROR_STR_CONNECTION_LOST; *tp_reason = TP_CONNECTION_STATUS_REASON_NETWORK_ERROR; break; case PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED: case PURPLE_CONNECTION_ERROR_INVALID_USERNAME: /* TODO: the following don't really match the tp reason but it's the * nearest match. Invalid settings shouldn't get this far in the first * place, and we ought to have some code for having no authentication * mechanisms in common with the server. But the latter is currently a * moot point since libpurple doesn't use this anywhere besides Jabber. */ case PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE: case PURPLE_CONNECTION_ERROR_INVALID_SETTINGS: /* TODO: This is not a very useful error. But it's fatal in libpurple — * it's used for things like the ICQ server telling you that you're * temporarily banned—so should map to a fatal error in Telepathy. * * We ought really to have an error case for unrecoverable server errors * where the best we can do is present a human-readable error from the * server. */ case PURPLE_CONNECTION_ERROR_OTHER_ERROR: set_both (AUTHENTICATION_FAILED); break; case PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT: *tp_reason = TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR; *tp_error_name = TP_ERROR_STR_ENCRYPTION_NOT_AVAILABLE; break; case PURPLE_CONNECTION_ERROR_NAME_IN_USE: if (while_connecting) *tp_error_name = TP_ERROR_STR_ALREADY_CONNECTED; else *tp_error_name = TP_ERROR_STR_CONNECTION_REPLACED; *tp_reason = TP_CONNECTION_STATUS_REASON_NAME_IN_USE; break; case PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR: *tp_reason = TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR; *tp_error_name = TP_ERROR_STR_CERT_INVALID; break; /* These members of the libpurple enum map 1-1 to the Telepathy enum and * to similarly-named D-Bus error names. */ trivial_case (ENCRYPTION_ERROR) trivial_case (CERT_NOT_PROVIDED) trivial_case (CERT_UNTRUSTED) trivial_case (CERT_EXPIRED) trivial_case (CERT_NOT_ACTIVATED) trivial_case (CERT_HOSTNAME_MISMATCH) trivial_case (CERT_FINGERPRINT_MISMATCH) trivial_case (CERT_SELF_SIGNED) default: g_warning ("report_disconnect_cb: invalid PurpleDisconnectReason %u", purple_reason); *tp_reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED; *tp_error_name = TP_ERROR_STR_DISCONNECTED; } #undef trivial_case #undef set_both } static void haze_report_disconnect_reason (PurpleConnection *gc, PurpleConnectionError reason, const char *text) { PurpleAccount *account = purple_connection_get_account (gc); HazeConnection *conn = ACCOUNT_GET_HAZE_CONNECTION (account); HazeConnectionPrivate *priv = conn->priv; TpBaseConnection *base_conn = ACCOUNT_GET_TP_BASE_CONNECTION (account); GHashTable *details; TpConnectionStatusReason tp_reason; const gchar *tp_error_name; /* When a connection error is reported by libpurple, an idle callback to * purple_account_disconnect is added. */ priv->disconnecting = TRUE; map_purple_error_to_tp (reason, (base_conn->status == TP_CONNECTION_STATUS_CONNECTING), &tp_reason, &tp_error_name); details = tp_asv_new ("debug-message", G_TYPE_STRING, text, NULL); tp_base_connection_disconnect_with_dbus_error (base_conn, tp_error_name, details, tp_reason); g_hash_table_unref (details); } static gboolean idle_finish_shutdown (gpointer data) { tp_base_connection_finish_shutdown (TP_BASE_CONNECTION (data)); return FALSE; } static void disconnected_cb (PurpleConnection *pc) { PurpleAccount *account = purple_connection_get_account (pc); HazeConnection *conn = ACCOUNT_GET_HAZE_CONNECTION (account); HazeConnectionPrivate *priv = conn->priv; TpBaseConnection *base_conn = ACCOUNT_GET_TP_BASE_CONNECTION (account); priv->disconnecting = TRUE; if(base_conn->status != TP_CONNECTION_STATUS_DISCONNECTED) { /* Because we have report_disconnect_reason, if status is not already * DISCONNECTED, we know that it was requested. */ tp_base_connection_change_status (base_conn, TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); } /* Call tp_base_connection_finish_shutdown () in an idle because calling it * might lead to the HazeConnection being destroyed, which would mean the * PurpleAccount were destroyed, but we're currently inside libpurple code * that uses it. */ g_idle_add (idle_finish_shutdown, conn); } static void _warn_unhandled_parameter (const gchar *key, const GValue *value, gpointer user_data) { g_warning ("received an unknown parameter '%s'; ignoring", key); } static void set_option ( PurpleAccount *account, const PurpleAccountOption *option, GHashTable *params) { if (g_hash_table_lookup (params, option->pref_name) == NULL) return; switch (option->type) { case PURPLE_PREF_BOOLEAN: purple_account_set_bool (account, option->pref_name, tp_asv_get_boolean (params, option->pref_name, NULL)); break; case PURPLE_PREF_INT: purple_account_set_int (account, option->pref_name, tp_asv_get_int32 (params, option->pref_name, NULL)); break; case PURPLE_PREF_STRING: case PURPLE_PREF_STRING_LIST: purple_account_set_string (account, option->pref_name, tp_asv_get_string (params, option->pref_name)); break; default: g_warning ("option '%s' has unhandled type %u", option->pref_name, option->type); } g_hash_table_remove (params, option->pref_name); } /** * haze_connection_create_account: * * Attempts to create a PurpleAccount corresponding to this connection. Must be * called immediately after constructing a connection. It's a shame GObject * constructors can't fail. * * Returns: %TRUE if the account was successfully created and hooked up; * %FALSE with @error set in the TP_ERROR domain if the account * already existed or another error occurred. */ gboolean haze_connection_create_account (HazeConnection *self, GError **error) { HazeConnectionPrivate *priv = self->priv; GHashTable *params = priv->parameters; PurplePluginProtocolInfo *prpl_info = priv->prpl_info; GList *l; g_return_val_if_fail (self->account == NULL, FALSE); if (purple_accounts_find (priv->username, priv->prpl_id) != NULL) { g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, "a connection already exists to %s on %s", priv->username, priv->prpl_id); return FALSE; } self->account = purple_account_new (priv->username, priv->prpl_id); purple_accounts_add (self->account); if (priv->password != NULL) purple_account_set_password (self->account, priv->password); self->account->ui_data = self; for (l = prpl_info->protocol_options; l != NULL; l = l->next) set_option (self->account, l->data, params); g_hash_table_foreach (params, (GHFunc) _warn_unhandled_parameter, "lala"); return TRUE; } static void _haze_connection_password_manager_prompt_cb (GObject *source, GAsyncResult *result, gpointer user_data) { HazeConnection *self = user_data; HazeConnectionPrivate *priv = self->priv; TpBaseConnection *base_conn = (TpBaseConnection *) self; const GString *password; GError *error = NULL; password = tp_simple_password_manager_prompt_finish ( TP_SIMPLE_PASSWORD_MANAGER (source), result, &error); if (error != NULL) { DEBUG ("Simple password manager failed: %s", error->message); if (priv->password_request) { haze_request_password_cb (priv->password_request, NULL); /* no need to call purple_account_disconnect(): the prpl will take * the account offline. If we're lucky it'll use an * AUTHENTICATION_FAILED-type message. */ } else if (base_conn->status != TP_CONNECTION_STATUS_DISCONNECTED) { tp_base_connection_disconnect_with_dbus_error (base_conn, tp_error_get_dbus_name (error->code), NULL, TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED); /* no need to call purple_account_disconnect because _connect * was never called ... */ } g_error_free (error); return; } g_free (priv->password); priv->password = g_strdup (password->str); if (priv->password_request) { haze_request_password_cb (priv->password_request, priv->password); } else { purple_account_set_password (self->account, priv->password); purple_account_set_enabled(self->account, UI_ID, TRUE); purple_account_connect (self->account); priv->connect_called = TRUE; } } static gboolean _haze_connection_start_connecting (TpBaseConnection *base, GError **error) { HazeConnection *self = HAZE_CONNECTION(base); HazeConnectionPrivate *priv = self->priv; TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); const gchar *password; g_return_val_if_fail (self->account != NULL, FALSE); base->self_handle = tp_handle_ensure (contact_handles, purple_account_get_username (self->account), NULL, error); if (!base->self_handle) return FALSE; tp_base_connection_change_status(base, TP_CONNECTION_STATUS_CONNECTING, TP_CONNECTION_STATUS_REASON_REQUESTED); /* We systematically enable mail notification to avoid bugs in protocol * like GMail and MySpace where you need to do an action before connecting * to start receiving the notifications. */ purple_account_set_check_mail(self->account, TRUE); /* check whether we need to pop up an auth channel */ password = purple_account_get_password (self->account); if (password == NULL && !(priv->prpl_info->options & OPT_PROTO_NO_PASSWORD) && !(priv->prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) { /* pop up auth channel */ tp_simple_password_manager_prompt_async (self->password_manager, _haze_connection_password_manager_prompt_cb, self); } else { purple_account_set_enabled(self->account, UI_ID, TRUE); purple_account_connect (self->account); priv->connect_called = TRUE; } return TRUE; } void haze_connection_request_password (PurpleAccount *account, void *user_data) { HazeConnection *self = ACCOUNT_GET_HAZE_CONNECTION (account); HazeConnectionPrivate *priv = self->priv; priv->password_request = user_data; /* pop up auth channel */ tp_simple_password_manager_prompt_async (self->password_manager, _haze_connection_password_manager_prompt_cb, self); } void haze_connection_cancel_password_request (PurpleAccount *account) { HazeConnection *self = ACCOUNT_GET_HAZE_CONNECTION (account); HazeConnectionPrivate *priv = self->priv; priv->password_request = NULL; } static void _haze_connection_shut_down (TpBaseConnection *base) { HazeConnection *self = HAZE_CONNECTION(base); HazeConnectionPrivate *priv = self->priv; if(!priv->disconnecting && priv->connect_called) { priv->disconnecting = TRUE; purple_account_disconnect(self->account); } else if (!priv->connect_called) { /* purple_account_connect was never actually called, so we * won't get to disconnected_cb and so finish_shutdown won't * be called unless we call it here. */ tp_base_connection_finish_shutdown (base); } } static gchar* _contact_normalize (TpHandleRepoIface *repo, const gchar *id, gpointer context, GError **error) { HazeConnection *conn = HAZE_CONNECTION (context); PurpleAccount *account = conn->account; return g_strdup (purple_normalize (account, id)); } static void _haze_connection_create_handle_repos (TpBaseConnection *base, TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) { repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, _contact_normalize, base); /* repos[TP_HANDLE_TYPE_ROOM] = XXX MUC */ } static GPtrArray * _haze_connection_create_channel_managers (TpBaseConnection *base) { HazeConnection *self = HAZE_CONNECTION(base); GPtrArray *channel_managers = g_ptr_array_new (); self->im_factory = HAZE_IM_CHANNEL_FACTORY ( g_object_new (HAZE_TYPE_IM_CHANNEL_FACTORY, "connection", self, NULL)); g_ptr_array_add (channel_managers, self->im_factory); #ifdef ENABLE_MEDIA /* Instantiate the media manager only if the protocol support calls */ if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC (self->priv->prpl_info, initiate_media)) { self->media_manager = HAZE_MEDIA_MANAGER ( g_object_new (HAZE_TYPE_MEDIA_MANAGER, "connection", self, NULL)); g_ptr_array_add (channel_managers, self->media_manager); } #endif self->contact_list = HAZE_CONTACT_LIST ( g_object_new (HAZE_TYPE_CONTACT_LIST, "connection", self, NULL)); g_ptr_array_add (channel_managers, self->contact_list); self->password_manager = tp_simple_password_manager_new ( TP_BASE_CONNECTION (self)); g_ptr_array_add (channel_managers, self->password_manager); return channel_managers; } static gchar * haze_connection_get_unique_connection_name(TpBaseConnection *base) { HazeConnection *self = HAZE_CONNECTION(base); return g_strdup (purple_account_get_username (self->account)); } static void haze_connection_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { HazeConnection *self = HAZE_CONNECTION (object); HazeConnectionPrivate *priv = self->priv; switch (property_id) { case PROP_PARAMETERS: g_value_set_boxed (value, priv->parameters); break; case PROP_USERNAME: g_value_set_string (value, priv->username); break; case PROP_PASSWORD: g_value_set_string (value, priv->password); break; case PROP_PRPL_ID: g_value_set_string (value, priv->prpl_id); break; case PROP_PRPL_INFO: g_value_set_pointer (value, priv->prpl_info); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void haze_connection_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { HazeConnection *self = HAZE_CONNECTION (object); HazeConnectionPrivate *priv = self->priv; switch (property_id) { case PROP_USERNAME: priv->username = g_value_dup_string (value); break; case PROP_PASSWORD: priv->password = g_value_dup_string (value); break; case PROP_PARAMETERS: priv->parameters = g_value_dup_boxed (value); break; case PROP_PRPL_ID: g_free (priv->prpl_id); priv->prpl_id = g_value_dup_string (value); break; case PROP_PRPL_INFO: priv->prpl_info = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static GObject * haze_connection_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { HazeConnection *self = HAZE_CONNECTION ( G_OBJECT_CLASS (haze_connection_parent_class)->constructor ( type, n_construct_properties, construct_params)); TpBaseConnection *base_conn = TP_BASE_CONNECTION (self); GObject *object = (GObject *) self; HazeConnectionPrivate *priv = self->priv; DEBUG ("Post-construction: (HazeConnection *)%p", self); self->acceptable_avatar_mime_types = NULL; priv->dispose_has_run = FALSE; priv->disconnecting = FALSE; tp_contacts_mixin_init (object, G_STRUCT_OFFSET (HazeConnection, contacts)); tp_base_connection_register_with_contacts_mixin (base_conn); tp_base_contact_list_mixin_register_with_contacts_mixin (base_conn); haze_connection_aliasing_init (object); haze_connection_avatars_init (object); haze_connection_capabilities_init (object); haze_connection_presence_init (object); haze_connection_mail_init (object); return (GObject *)self; } static void haze_connection_dispose (GObject *object) { HazeConnection *self = HAZE_CONNECTION(object); HazeConnectionPrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; DEBUG ("disposing of (HazeConnection *)%p", self); g_hash_table_unref (priv->parameters); priv->parameters = NULL; G_OBJECT_CLASS (haze_connection_parent_class)->dispose (object); } static void haze_connection_finalize (GObject *object) { HazeConnection *self = HAZE_CONNECTION (object); HazeConnectionPrivate *priv = self->priv; tp_contacts_mixin_finalize (object); tp_presence_mixin_finalize (object); haze_connection_capabilities_finalize (object); g_strfreev (self->acceptable_avatar_mime_types); g_free (priv->username); g_free (priv->password); if (self->account != NULL) { DEBUG ("deleting account %s", self->account->username); purple_accounts_delete (self->account); } G_OBJECT_CLASS (haze_connection_parent_class)->finalize (object); } static void haze_connection_class_init (HazeConnectionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); TpBaseConnectionClass *base_class = TP_BASE_CONNECTION_CLASS (klass); GParamSpec *param_spec; static TpDBusPropertiesMixinPropImpl mail_props[] = { { "MailNotificationFlags", NULL, NULL }, { "UnreadMailCount", NULL, NULL }, { "UnreadMails", NULL, NULL }, { "MailAddress", NULL, NULL }, { NULL } }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { { TP_IFACE_CONNECTION_INTERFACE_AVATARS, haze_connection_avatars_properties_getter, NULL, NULL }, /* initialized a bit later */ { HAZE_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, haze_connection_mail_properties_getter, NULL, mail_props, }, { NULL } }; DEBUG ("Initializing (HazeConnectionClass *)%p", klass); g_type_class_add_private (klass, sizeof (HazeConnectionPrivate)); object_class->get_property = haze_connection_get_property; object_class->set_property = haze_connection_set_property; object_class->constructor = haze_connection_constructor; object_class->dispose = haze_connection_dispose; object_class->finalize = haze_connection_finalize; base_class->create_handle_repos = _haze_connection_create_handle_repos; base_class->create_channel_managers = _haze_connection_create_channel_managers; base_class->get_unique_connection_name = haze_connection_get_unique_connection_name; base_class->start_connecting = _haze_connection_start_connecting; base_class->shut_down = _haze_connection_shut_down; base_class->interfaces_always_present = haze_connection_get_guaranteed_interfaces(); param_spec = g_param_spec_boxed ("parameters", "gchar * => GValue", "Connection parameters (password, etc.)", G_TYPE_HASH_TABLE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_PARAMETERS, param_spec); param_spec = g_param_spec_string ("username", "username", "protocol plugin username", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_USERNAME, param_spec); param_spec = g_param_spec_string ("password", "password", "protocol plugin password, or NULL if none", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_PASSWORD, param_spec); param_spec = g_param_spec_string ("prpl-id", "protocol plugin ID", "protocol plugin ID", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_PRPL_ID, param_spec); param_spec = g_param_spec_pointer ("prpl-info", "PurplePluginProtocolInfo", "protocol plugin info", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_PRPL_INFO, param_spec); prop_interfaces[0].props = haze_connection_avatars_properties; klass->properties_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (HazeConnectionClass, properties_class)); tp_contacts_mixin_class_init (object_class, G_STRUCT_OFFSET (HazeConnectionClass, contacts_class)); tp_base_contact_list_mixin_class_init (base_class); haze_connection_presence_class_init (object_class); haze_connection_aliasing_class_init (object_class); haze_connection_avatars_class_init (object_class); haze_connection_capabilities_class_init (object_class); } static void haze_connection_init (HazeConnection *self) { DEBUG ("Initializing (HazeConnection *)%p", self); self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, HAZE_TYPE_CONNECTION, HazeConnectionPrivate); } static PurpleAccountUiOps account_ui_ops = { NULL, /* notify_added */ haze_connection_presence_account_status_changed, /* status_changed */ NULL, /* request_add */ haze_request_authorize, /* request_authorize */ haze_close_account_request, /* close_account_request */ NULL, /* purple_reserved1 */ NULL, /* purple_reserved2 */ NULL, /* purple_reserved3 */ NULL /* purple_reserved4 */ }; PurpleAccountUiOps * haze_get_account_ui_ops () { return &account_ui_ops; } static PurpleConnectionUiOps connection_ui_ops = { NULL, /* connect_progress */ connected_cb, /* connected */ disconnected_cb, /* disconnected */ NULL, /* notice */ NULL, /* report_disconnect */ NULL, /* network_connected */ NULL, /* network_disconnected */ haze_report_disconnect_reason, /* report_disconnect_reason */ NULL, /* _purple_reserved1 */ NULL, /* _purple_reserved2 */ NULL /* _purple_reserved3 */ }; PurpleConnectionUiOps * haze_get_connection_ui_ops () { return &connection_ui_ops; } const gchar * haze_connection_handle_inspect (HazeConnection *conn, TpHandleType handle_type, TpHandle handle) { TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn); TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn, handle_type); g_assert (tp_handle_is_valid (handle_repo, handle, NULL)); return tp_handle_inspect (handle_repo, handle); } /** * Get the group that "most" libpurple prpls will use for ungrouped contacts. */ const gchar * haze_get_fallback_group (void) { return dgettext ("pidgin", "Buddies"); }