X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=src%2Fempathy-chat-manager.c;h=279d7d8ff43bcdf31716067920e6a55f93aec8bd;hp=c3488dc4511b102129f6df913fba33f245871f76;hb=21674ad377f646ef1e242f7e4260ee2398a220be;hpb=23d90f884b28cabd3b9b986998c118314a9238f9 diff --git a/src/empathy-chat-manager.c b/src/empathy-chat-manager.c index c3488dc4..279d7d8f 100644 --- a/src/empathy-chat-manager.c +++ b/src/empathy-chat-manager.c @@ -17,28 +17,38 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include +#include "config.h" +#include "empathy-chat-manager.h" -#include -#include -#include +#include +#include +#include "empathy-chatroom-manager.h" #include "empathy-chat-window.h" +#include "empathy-request-util.h" +#include "empathy-ui-utils.h" +#include "extensions.h" #define DEBUG_FLAG EMPATHY_DEBUG_OTHER -#include +#include "empathy-debug.h" -#include "empathy-chat-manager.h" +#define CHAT_MANAGER_PATH "/org/gnome/Empathy/ChatManager" enum { CLOSED_CHATS_CHANGED, - HANDLED_CHATS_CHANGED, + DISPLAYED_CHATS_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; -G_DEFINE_TYPE(EmpathyChatManager, empathy_chat_manager, G_TYPE_OBJECT) +static void svc_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (EmpathyChatManager, empathy_chat_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (EMP_TYPE_SVC_CHAT_MANAGER, + svc_iface_init) + ) /* private structure */ typedef struct _EmpathyChatManagerPriv EmpathyChatManagerPriv; @@ -49,9 +59,19 @@ struct _EmpathyChatManagerPriv /* Queue of (ChatData *) representing the closed chats */ GQueue *closed_queue; - guint num_handled_channels; + guint num_displayed_chat; + + /* account path -> (GHashTable<(owned gchar *) contact ID + * -> (owned gchar *) non-NULL message>) + */ + GHashTable *messages; TpBaseClient *handler; + + /* Cached to keep Folks in memory while empathy-chat is running; we don't + * want to reload it each time the last chat window is closed + * and re-opened. */ + EmpathyIndividualManager *individual_mgr; }; #define GET_PRIV(o) \ @@ -65,6 +85,7 @@ typedef struct TpAccount *account; gchar *id; gboolean room; + gboolean sms; } ChatData; static ChatData * @@ -77,6 +98,7 @@ chat_data_new (EmpathyChat *chat) data->account = g_object_ref (empathy_chat_get_account (chat)); data->id = g_strdup (empathy_chat_get_id (chat)); data->room = empathy_chat_is_room (chat); + data->sms = empathy_chat_is_sms_channel (chat); return data; } @@ -99,19 +121,68 @@ chat_data_free (ChatData *data) g_slice_free (ChatData, data); } +static void +chat_destroyed_cb (gpointer data, + GObject *object) +{ + EmpathyChatManager *self = data; + EmpathyChatManagerPriv *priv = GET_PRIV (self); + + priv->num_displayed_chat--; + + DEBUG ("Chat destroyed; we are now displaying %u chats", + priv->num_displayed_chat); + + g_signal_emit (self, signals[DISPLAYED_CHATS_CHANGED], 0, + priv->num_displayed_chat); +} + +static void +join_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + TpChannel *channel = TP_CHANNEL (source); + GError *error = NULL; + + if (!tp_channel_join_finish (channel, result, &error)) + { + DEBUG ("Failed to join chat (%s): %s", + tp_channel_get_identifier (channel), error->message); + g_error_free (error); + } +} + +static void +individual_mgr_cb (EmpathyChatWindow *window, + GParamSpec *spec, + EmpathyChatManager *self) +{ + EmpathyChatManagerPriv *priv = GET_PRIV (self); + + if (priv->individual_mgr != NULL) + return; + + priv->individual_mgr = empathy_chat_window_get_individual_manager (window); + g_object_ref (priv->individual_mgr); +} + static void process_tp_chat (EmpathyChatManager *self, EmpathyTpChat *tp_chat, TpAccount *account, gint64 user_action_time) { + EmpathyChatManagerPriv *priv = GET_PRIV (self); EmpathyChat *chat = NULL; const gchar *id; + EmpathyChatWindow *window; id = empathy_tp_chat_get_id (tp_chat); if (!tp_str_empty (id)) { - chat = empathy_chat_window_find_chat (account, id); + chat = empathy_chat_window_find_chat (account, id, + tp_text_channel_is_sms_channel ((TpTextChannel *) tp_chat)); } if (chat != NULL) @@ -120,92 +191,51 @@ process_tp_chat (EmpathyChatManager *self, } else { + GHashTable *chats = NULL; + chat = empathy_chat_new (tp_chat); /* empathy_chat_new returns a floating reference as EmpathyChat is * a GtkWidget. This reference will be taken by a container * (a GtkNotebook) when we'll call empathy_chat_window_present_chat */ - } - empathy_chat_window_present_chat (chat, user_action_time); - if (empathy_tp_chat_is_invited (tp_chat, NULL)) - { - /* We have been invited to the room. Add ourself as member as this - * channel has been approved. */ - empathy_tp_chat_join (tp_chat); - } + priv->num_displayed_chat++; - g_object_unref (tp_chat); -} + DEBUG ("Chat displayed; we are now displaying %u chat", + priv->num_displayed_chat); -typedef struct -{ - EmpathyChatManager *self; - EmpathyTpChat *tp_chat; - TpAccount *account; - gint64 user_action_time; - gulong sig_id; -} chat_ready_ctx; - -static chat_ready_ctx * -chat_ready_ctx_new (EmpathyChatManager *self, - EmpathyTpChat *tp_chat, - TpAccount *account, - gint64 user_action_time) -{ - chat_ready_ctx *ctx = g_slice_new0 (chat_ready_ctx); + g_signal_emit (self, signals[DISPLAYED_CHATS_CHANGED], 0, + priv->num_displayed_chat); - ctx->self = g_object_ref (self); - ctx->tp_chat = g_object_ref (tp_chat); - ctx->account = g_object_ref (account); - ctx->user_action_time = user_action_time; - return ctx; -} + /* Set the saved message in the channel if we have one. */ + chats = g_hash_table_lookup (priv->messages, + tp_proxy_get_object_path (account)); -static void -chat_ready_ctx_free (chat_ready_ctx *ctx) -{ - g_object_unref (ctx->self); - g_object_unref (ctx->tp_chat); - g_object_unref (ctx->account); - - if (ctx->sig_id != 0) - g_signal_handler_disconnect (ctx->tp_chat, ctx->sig_id); - - g_slice_free (chat_ready_ctx, ctx); -} - -static void -tp_chat_ready_cb (GObject *object, - GParamSpec *spec, - gpointer user_data) -{ - EmpathyTpChat *tp_chat = EMPATHY_TP_CHAT (object); - chat_ready_ctx *ctx = user_data; - - if (!empathy_tp_chat_is_ready (tp_chat)) - return; - - process_tp_chat (ctx->self, tp_chat, ctx->account, ctx->user_action_time); + if (chats != NULL) + { + const gchar *msg = g_hash_table_lookup (chats, id); - chat_ready_ctx_free (ctx); -} + if (msg != NULL) + empathy_chat_set_text (chat, msg); + } -static void -channel_invalidated (TpChannel *channel, - guint domain, - gint code, - gchar *message, - EmpathyChatManager *self) -{ - EmpathyChatManagerPriv *priv = GET_PRIV (self); + g_object_weak_ref ((GObject *) chat, chat_destroyed_cb, self); + } - priv->num_handled_channels--; + window = empathy_chat_window_present_chat (chat, user_action_time); - DEBUG ("Channel closed; we are now handling %u text channels", - priv->num_handled_channels); + if (priv->individual_mgr == NULL) + { + /* We want to cache it as soon it's created */ + tp_g_signal_connect_object (window, "notify::individual-manager", + G_CALLBACK (individual_mgr_cb), self, 0); + } - g_signal_emit (self, signals[HANDLED_CHATS_CHANGED], 0, - priv->num_handled_channels); + if (empathy_tp_chat_is_invited (tp_chat, NULL)) + { + /* We have been invited to the room. Add ourself as member as this + * channel has been approved. */ + tp_channel_join_async (TP_CHANNEL (tp_chat), "", join_cb, self); + } } static void @@ -219,83 +249,50 @@ handle_channels (TpSimpleHandler *handler, gpointer user_data) { EmpathyChatManager *self = (EmpathyChatManager *) user_data; - EmpathyChatManagerPriv *priv = GET_PRIV (self); GList *l; - gboolean handling = FALSE; for (l = channels; l != NULL; l = g_list_next (l)) { - TpChannel *channel = l->data; - EmpathyTpChat *tp_chat; + EmpathyTpChat *tp_chat = l->data; - if (tp_proxy_get_invalidated (channel) != NULL) + if (tp_proxy_get_invalidated (tp_chat) != NULL) continue; - handling = TRUE; - - tp_chat = empathy_tp_chat_new (account, channel); - - if (empathy_tp_chat_is_ready (tp_chat)) - { - process_tp_chat (self, tp_chat, account, user_action_time); - } - else + if (!EMPATHY_IS_TP_CHAT (tp_chat)) { - chat_ready_ctx *ctx = chat_ready_ctx_new (self, tp_chat, account, - user_action_time); - - ctx->sig_id = g_signal_connect (tp_chat, "notify::ready", - G_CALLBACK (tp_chat_ready_cb), ctx); + DEBUG ("Channel %s doesn't implement Messages; can't handle it", + tp_proxy_get_object_path (tp_chat)); + continue; } - priv->num_handled_channels++; + DEBUG ("Now handling channel %s", tp_proxy_get_object_path (tp_chat)); - g_signal_connect (channel, "invalidated", - G_CALLBACK (channel_invalidated), self); + process_tp_chat (self, tp_chat, account, user_action_time); } tp_handle_channels_context_accept (context); - - if (handling) - { - DEBUG ("Channels handled; we are now handling %u text channels", - priv->num_handled_channels); - - g_signal_emit (self, signals[HANDLED_CHATS_CHANGED], 0, - priv->num_handled_channels); - } } static void empathy_chat_manager_init (EmpathyChatManager *self) { EmpathyChatManagerPriv *priv = GET_PRIV (self); - TpDBusDaemon *dbus; + TpAccountManager *am; GError *error = NULL; priv->closed_queue = g_queue_new (); + priv->messages = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_hash_table_unref); - dbus = tp_dbus_daemon_dup (&error); - if (dbus == NULL) - { - g_critical ("Failed to get D-Bus daemon: %s", error->message); - g_error_free (error); - return; - } + am = tp_account_manager_dup (); priv->chatroom_mgr = empathy_chatroom_manager_dup_singleton (NULL); /* Text channels handler */ - priv->handler = tp_simple_handler_new (dbus, FALSE, FALSE, "Empathy.Chat", - FALSE, handle_channels, self, NULL); - - /* EmpathyTpChat relies on these features being prepared */ - tp_base_client_add_connection_features_varargs (priv->handler, - TP_CONNECTION_FEATURE_CAPABILITIES, 0); - tp_base_client_add_channel_features_varargs (priv->handler, - TP_CHANNEL_FEATURE_CHAT_STATES, 0); + priv->handler = tp_simple_handler_new_with_am (am, FALSE, FALSE, + EMPATHY_CHAT_BUS_NAME_SUFFIX, FALSE, handle_channels, self, NULL); - g_object_unref (dbus); + g_object_unref (am); tp_base_client_take_handler_filter (priv->handler, tp_asv_new ( TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT, @@ -332,8 +329,11 @@ empathy_chat_manager_finalize (GObject *object) priv->closed_queue = NULL; } + tp_clear_pointer (&priv->messages, g_hash_table_unref); + tp_clear_object (&priv->handler); tp_clear_object (&priv->chatroom_mgr); + tp_clear_object (&priv->individual_mgr); G_OBJECT_CLASS (empathy_chat_manager_parent_class)->finalize (object); } @@ -361,6 +361,22 @@ empathy_chat_manager_constructor (GType type, return retval; } +static void +empathy_chat_manager_constructed (GObject *obj) +{ + TpDBusDaemon *dbus_daemon; + + dbus_daemon = tp_dbus_daemon_dup (NULL); + + if (dbus_daemon != NULL) + { + tp_dbus_daemon_register_object (dbus_daemon, + CHAT_MANAGER_PATH, obj); + + g_object_unref (dbus_daemon); + } +} + static void empathy_chat_manager_class_init ( EmpathyChatManagerClass *empathy_chat_manager_class) @@ -369,6 +385,7 @@ empathy_chat_manager_class_init ( object_class->finalize = empathy_chat_manager_finalize; object_class->constructor = empathy_chat_manager_constructor; + object_class->constructed = empathy_chat_manager_constructed; signals[CLOSED_CHATS_CHANGED] = g_signal_new ("closed-chats-changed", @@ -376,17 +393,17 @@ empathy_chat_manager_class_init ( G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__UINT, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT, NULL); - signals[HANDLED_CHATS_CHANGED] = - g_signal_new ("handled-chats-changed", + signals[DISPLAYED_CHATS_CHANGED] = + g_signal_new ("displayed-chats-changed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__UINT, + g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT, NULL); @@ -406,6 +423,8 @@ empathy_chat_manager_closed_chat (EmpathyChatManager *self, { EmpathyChatManagerPriv *priv = GET_PRIV (self); ChatData *data; + GHashTable *chats; + gchar *message; data = chat_data_new (chat); @@ -416,10 +435,48 @@ empathy_chat_manager_closed_chat (EmpathyChatManager *self, g_signal_emit (self, signals[CLOSED_CHATS_CHANGED], 0, g_queue_get_length (priv->closed_queue)); + + /* If there was a message saved from last time it was closed + * (perhaps by accident?) save it to our hash table so it can be + * used again when the same chat pops up. Hot. */ + message = empathy_chat_dup_text (chat); + + chats = g_hash_table_lookup (priv->messages, + tp_proxy_get_object_path (data->account)); + + /* Don't create a new hash table if we don't already have one and we + * don't actually have a message to save. */ + if (chats == NULL && tp_str_empty (message)) + { + g_free (message); + return; + } + else if (chats == NULL && !tp_str_empty (message)) + { + chats = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_insert (priv->messages, + g_strdup (tp_proxy_get_object_path (data->account)), + chats); + } + + if (tp_str_empty (message)) + { + g_hash_table_remove (chats, data->id); + /* might be '\0' */ + g_free (message); + } + else + { + /* takes ownership of message */ + g_hash_table_insert (chats, g_strdup (data->id), message); + } } void -empathy_chat_manager_undo_closed_chat (EmpathyChatManager *self) +empathy_chat_manager_undo_closed_chat (EmpathyChatManager *self, + gint64 timestamp) { EmpathyChatManagerPriv *priv = GET_PRIV (self); ChatData *data; @@ -433,11 +490,13 @@ empathy_chat_manager_undo_closed_chat (EmpathyChatManager *self) data->room ? "room" : "contact", data->id); if (data->room) - empathy_join_muc (data->account, data->id, - TP_USER_ACTION_TIME_NOT_USER_ACTION); + empathy_join_muc (data->account, data->id, timestamp); + else if (data->sms) + empathy_sms_contact_id (data->account, data->id, timestamp, + NULL, NULL); else - empathy_chat_with_contact_id (data->account, data->id, - TP_USER_ACTION_TIME_NOT_USER_ACTION); + empathy_chat_with_contact_id (data->account, data->id, timestamp, + NULL, NULL); g_signal_emit (self, signals[CLOSED_CHATS_CHANGED], 0, g_queue_get_length (priv->closed_queue)); @@ -453,10 +512,50 @@ empathy_chat_manager_get_num_closed_chats (EmpathyChatManager *self) return g_queue_get_length (priv->closed_queue); } -guint -empathy_chat_manager_get_num_handled_chats (EmpathyChatManager *self) +static void +empathy_chat_manager_dbus_undo_closed_chat (EmpSvcChatManager *manager, + gint64 timestamp, + DBusGMethodInvocation *context) { - EmpathyChatManagerPriv *priv = GET_PRIV (self); + empathy_chat_manager_undo_closed_chat ((EmpathyChatManager *) manager, + timestamp); + + emp_svc_chat_manager_return_from_undo_closed_chat (context); +} + +static void +svc_iface_init (gpointer g_iface, + gpointer iface_data) +{ + EmpSvcChatManagerClass *klass = (EmpSvcChatManagerClass *) g_iface; + +#define IMPLEMENT(x) emp_svc_chat_manager_implement_##x (\ + klass, empathy_chat_manager_dbus_##x) + IMPLEMENT(undo_closed_chat); +#undef IMPLEMENT +} + +void +empathy_chat_manager_call_undo_closed_chat (void) +{ + TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL); + TpProxy *proxy; + + if (dbus_daemon == NULL) + return; + + proxy = g_object_new (TP_TYPE_PROXY, + "dbus-daemon", dbus_daemon, + "dbus-connection", tp_proxy_get_dbus_connection (TP_PROXY (dbus_daemon)), + "bus-name", EMPATHY_CHAT_BUS_NAME, + "object-path", CHAT_MANAGER_PATH, + NULL); + + tp_proxy_add_interface_by_id (proxy, EMP_IFACE_QUARK_CHAT_MANAGER); + + emp_cli_chat_manager_call_undo_closed_chat (proxy, -1, empathy_get_current_action_time (), + NULL, NULL, NULL, NULL); - return priv->num_handled_channels; + g_object_unref (proxy); + g_object_unref (dbus_daemon); }