From 998c5f17fc95a77821115bacc75ac344b406377c Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Thu, 30 Jun 2011 14:05:41 +0200 Subject: [PATCH] Approve room invitations We use to rely on Empathy for this but as we plan to allow users to be connected on IM without having Empathy running the Shell should do it now. https://bugzilla.gnome.org/show_bug.cgi?id=653740 --- js/ui/notificationDaemon.js | 3 +- js/ui/telepathyClient.js | 147 +++++++++++++++++++++++++++++++++--- src/shell-tp-client.c | 21 ++++++ src/shell-tp-client.h | 3 + 4 files changed, 163 insertions(+), 11 deletions(-) diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 35eb58166..66bd0c58f 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -190,9 +190,10 @@ NotificationDaemon.prototype = { actions, hints, timeout) { let id; - // Filter out chat and presence notifications from Empathy, since we + // Filter out chat, presence and invitation notifications from Empathy, since we // handle that information from telepathyClient.js if (appName == 'Empathy' && (hints['category'] == 'im.received' || + hints['category'] == 'x-empathy.im.room-invitation' || hints['category'] == 'presence.online' || hints['category'] == 'presence.offline')) { // Ignore replacesId since we already sent back a diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js index d81b40a76..8a476011a 100644 --- a/js/ui/telepathyClient.js +++ b/js/ui/telepathyClient.js @@ -178,19 +178,63 @@ Client.prototype = { } }, + _displayRoomInvitation: function(conn, channel, dispatchOp, context) { + // We can only approve the rooms if we have been invited to it + let selfHandle = channel.group_get_self_handle(); + if (selfHandle == 0) { + Shell.decline_dispatch_op(context, 'Not invited to the room'); + return; + } + + let [invited, inviter, reason, msg] = channel.group_get_local_pending_info(selfHandle); + if (!invited) { + Shell.decline_dispatch_op(context, 'Not invited to the room'); + return; + } + + // Request a TpContact for the inviter + Shell.get_tp_contacts(conn, [inviter], + contactFeatures, + Lang.bind(this, this._createRoomInviteSource, channel, context, dispatchOp)); + + context.delay(); + }, + + _createRoomInviteSource: function(connection, contacts, failed, channel, context, dispatchOp) { + if (contacts.length < 1) { + Shell.decline_dispatch_op(context, 'Failed to get inviter'); + return; + } + + // We got the TpContact + let source = new RoomInviteSource(dispatchOp); + Main.messageTray.add(source); + + let notif = new RoomInviteNotification(source, dispatchOp, channel, contacts[0]); + source.notify(notif); + context.accept(); + }, + _approveChannels: function(approver, account, conn, channels, dispatchOp, context) { - // Approve the channels right away as we are going to handle it - dispatchOp.claim_with_async(this._tpClient, - Lang.bind (this, function(dispatchOp, result) { - try { - dispatchOp.claim_with_finish(result); - this._handlingChannels(account, conn, channels); - } catch (err) { - global.logError('Failed to Claim channel: ' + err); - }})); + let channel = channels[0]; + let [targetHandle, targetHandleType] = channel.get_handle(); - context.accept(); + if (targetHandleType == Tp.HandleType.CONTACT) { + // Approve private text channels right away as we are going to handle it + dispatchOp.claim_with_async(this._tpClient, + Lang.bind(this, function(dispatchOp, result) { + try { + dispatchOp.claim_with_finish(result); + this._handlingChannels(account, conn, channels); + } catch (err) { + throw new Error('Failed to Claim channel: ' + err); + }})); + + context.accept(); + } else { + this._displayRoomInvitation(conn, channel, dispatchOp, context); + } }, _handleChannels: function(handler, account, conn, channels, @@ -684,3 +728,86 @@ ChatNotification.prototype = { this.source.respond(text); } }; + +function RoomInviteSource(dispatchOp) { + this._init(dispatchOp); +} + +RoomInviteSource.prototype = { + __proto__: MessageTray.Source.prototype, + + _init: function(dispatchOp) { + MessageTray.Source.prototype._init.call(this, _("Invitation")); + + this._setSummaryIcon(this.createNotificationIcon()); + + this._dispatchOp = dispatchOp; + + // Destroy the source if the channel dispatch operation is invalidated + // as we can't approve any more. + this._invalidId = dispatchOp.connect('invalidated', + Lang.bind(this, function(domain, code, msg) { + this.destroy(); + })); + }, + + destroy: function() { + if (this._invalidId != 0) { + this._dispatchOp.disconnect(this._invalidId); + this._invalidId = 0; + } + + MessageTray.Source.prototype.destroy.call(this); + }, + + createNotificationIcon: function() { + // FIXME: We don't have a 'chat room' icon (bgo #653737) use + // system-users for now as Empathy does. + return new St.Icon({ icon_name: 'system-users', + icon_type: St.IconType.FULLCOLOR, + icon_size: this.ICON_SIZE }); + } +} + +function RoomInviteNotification(source, dispatchOp, channel, inviter) { + this._init(source, dispatchOp, channel, inviter); +} + +RoomInviteNotification.prototype = { + __proto__: MessageTray.Notification.prototype, + + _init: function(source, dispatchOp, channel, inviter) { + MessageTray.Notification.prototype._init.call(this, + source, + /* translators: argument is a room name like + * room@jabber.org for example. */ + _("Invitation to %s").format(channel.get_identifier()), + null, + { customContent: true }); + this.setResident(true); + + /* translators: first argument is the name of a contact and the second + * one the name of a room. "Alice is inviting you to join room@jabber.org + * for example. */ + this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier())); + + this.addButton('decline', _("Decline")); + this.addButton('accept', _("Accept")); + + this.connect('action-invoked', Lang.bind(this, function(self, action) { + switch (action) { + case 'decline': + dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, + '', function(src, result) { + src.leave_channels_finish(result)}); + break; + case 'accept': + dispatchOp.handle_with_time_async('', global.get_current_time(), + function(src, result) { + src.handle_with_time_finish(result)}); + break; + } + this.destroy(); + })); + } +}; diff --git a/src/shell-tp-client.c b/src/shell-tp-client.c index af507bc88..018f233e5 100644 --- a/src/shell-tp-client.c +++ b/src/shell-tp-client.c @@ -101,6 +101,15 @@ shell_tp_client_init (ShellTpClient *self) /* Approver */ tp_base_client_add_approver_filter (TP_BASE_CLIENT (self), filter); + /* Approve room invitations. We don't handle or observe room channels so + * just register this filter for the approver. */ + tp_base_client_take_approver_filter (TP_BASE_CLIENT (self), tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, + TP_IFACE_CHANNEL_TYPE_TEXT, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, + TP_HANDLE_TYPE_ROOM, + NULL)); + /* Handler */ tp_base_client_add_handler_filter (TP_BASE_CLIENT (self), filter); @@ -371,3 +380,15 @@ shell_get_contact_events (TplLogManager *log_manager, NULL, NULL, callback, NULL); } + +/* gjs doesn't allow us to craft a GError so we need a C wrapper */ +void +shell_decline_dispatch_op (TpAddDispatchOperationContext *context, + const gchar *message) +{ + GError *error = g_error_new_literal (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + message); + + tp_add_dispatch_operation_context_fail (context, error); + g_error_free (error); +} diff --git a/src/shell-tp-client.h b/src/shell-tp-client.h index 064c6abe5..b32304587 100644 --- a/src/shell-tp-client.h +++ b/src/shell-tp-client.h @@ -111,5 +111,8 @@ void shell_get_contact_events (TplLogManager *log_manager, guint num_events, GAsyncReadyCallback callback); +void shell_decline_dispatch_op (TpAddDispatchOperationContext *context, + const gchar *message); + G_END_DECLS #endif /* __SHELL_TP_CLIENT_H__ */