telepathyClient: Add IM subscription request support
Based on initial work from Guillaume Desmottes https://bugzilla.gnome.org/show_bug.cgi?id=653941
This commit is contained in:
parent
071c49b7c6
commit
fefee3b49e
@ -1257,6 +1257,10 @@ StTooltip StLabel {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subscription-message {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
#notification StEntry {
|
#notification StEntry {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -196,6 +196,7 @@ NotificationDaemon.prototype = {
|
|||||||
hints['category'] == 'x-empathy.im.room-invitation' ||
|
hints['category'] == 'x-empathy.im.room-invitation' ||
|
||||||
hints['category'] == 'x-empathy.call.incoming' ||
|
hints['category'] == 'x-empathy.call.incoming' ||
|
||||||
hints['category'] == 'x-empathy.call.incoming"' ||
|
hints['category'] == 'x-empathy.call.incoming"' ||
|
||||||
|
hints['category'] == 'x-empathy.im.subscription-request' ||
|
||||||
hints['category'] == 'presence.online' ||
|
hints['category'] == 'presence.online' ||
|
||||||
hints['category'] == 'presence.offline')) {
|
hints['category'] == 'presence.offline')) {
|
||||||
// Ignore replacesId since we already sent back a
|
// Ignore replacesId since we already sent back a
|
||||||
|
@ -87,8 +87,8 @@ Client.prototype = {
|
|||||||
// channel matching its filters is detected.
|
// channel matching its filters is detected.
|
||||||
// The second argument, recover, means _observeChannels will be run
|
// The second argument, recover, means _observeChannels will be run
|
||||||
// for any existing channel as well.
|
// for any existing channel as well.
|
||||||
let dbus = Tp.DBusDaemon.dup();
|
this._accountManager = Tp.AccountManager.dup();
|
||||||
this._tpClient = new Shell.TpClient({ 'dbus_daemon': dbus,
|
this._tpClient = new Shell.TpClient({ 'account-manager': this._accountManager,
|
||||||
'name': 'GnomeShell',
|
'name': 'GnomeShell',
|
||||||
'uniquify-name': true })
|
'uniquify-name': true })
|
||||||
this._tpClient.set_observe_channels_func(
|
this._tpClient.set_observe_channels_func(
|
||||||
@ -98,6 +98,11 @@ Client.prototype = {
|
|||||||
this._tpClient.set_handle_channels_func(
|
this._tpClient.set_handle_channels_func(
|
||||||
Lang.bind(this, this._handleChannels));
|
Lang.bind(this, this._handleChannels));
|
||||||
|
|
||||||
|
// Workaround for gjs not supporting GPtrArray in signals.
|
||||||
|
// See BGO bug #653941 for context.
|
||||||
|
this._tpClient.set_contact_list_changed_func(
|
||||||
|
Lang.bind(this, this._contactListChanged));
|
||||||
|
|
||||||
// Allow other clients (such as Empathy) to pre-empt our channels if
|
// Allow other clients (such as Empathy) to pre-empt our channels if
|
||||||
// needed
|
// needed
|
||||||
this._tpClient.set_delegated_channels_callback(
|
this._tpClient.set_delegated_channels_callback(
|
||||||
@ -108,6 +113,21 @@ Client.prototype = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
|
throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Watch subscription requests and connection errors
|
||||||
|
this._subscriptionSource = null;
|
||||||
|
let factory = this._accountManager.get_factory();
|
||||||
|
factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
|
||||||
|
factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
|
||||||
|
factory.add_contact_features([Tp.ContactFeature.SUBSCRIPTION_STATES,
|
||||||
|
Tp.ContactFeature.ALIAS,
|
||||||
|
Tp.ContactFeature.AVATAR_DATA]);
|
||||||
|
|
||||||
|
this._accountManager.connect('account-validity-changed',
|
||||||
|
Lang.bind(this, this._accountValidityChanged));
|
||||||
|
|
||||||
|
this._accountManager.prepare_async(null, Lang.bind(this, this._accountManagerPrepared));
|
||||||
},
|
},
|
||||||
|
|
||||||
_observeChannels: function(observer, account, conn, channels,
|
_observeChannels: function(observer, account, conn, channels,
|
||||||
@ -337,6 +357,79 @@ Client.prototype = {
|
|||||||
_delegatedChannelsCb: function(client, channels) {
|
_delegatedChannelsCb: function(client, channels) {
|
||||||
// Nothing to do as we don't make a distinction between observed and
|
// Nothing to do as we don't make a distinction between observed and
|
||||||
// handled channels.
|
// handled channels.
|
||||||
|
},
|
||||||
|
|
||||||
|
_accountManagerPrepared: function(am, result) {
|
||||||
|
am.prepare_finish(result);
|
||||||
|
|
||||||
|
let accounts = am.get_valid_accounts();
|
||||||
|
for (let i = 0; i < accounts.length; i++) {
|
||||||
|
this._accountValidityChanged(am, accounts[i], true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_accountValidityChanged: function(am, account, valid) {
|
||||||
|
if (!valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
account.connect('notify::connection',
|
||||||
|
Lang.bind(this, this._connectionChanged));
|
||||||
|
this._connectionChanged(account);
|
||||||
|
},
|
||||||
|
|
||||||
|
_connectionChanged: function(account) {
|
||||||
|
let conn = account.get_connection();
|
||||||
|
if (conn == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._tpClient.grab_contact_list_changed(conn);
|
||||||
|
if (conn.get_contact_list_state() == Tp.ContactListState.SUCCESS) {
|
||||||
|
this._contactListChanged(conn, conn.dup_contact_list(), []);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_contactListChanged: function(conn, added, removed) {
|
||||||
|
for (let i = 0; i < added.length; i++) {
|
||||||
|
let contact = added[i];
|
||||||
|
|
||||||
|
contact.connect('subscription-states-changed',
|
||||||
|
Lang.bind(this, this._subscriptionStateChanged));
|
||||||
|
this._subscriptionStateChanged(contact);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_subscriptionStateChanged: function(contact) {
|
||||||
|
if (contact.get_publish_state() != Tp.SubscriptionState.ASK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Implicitly accept publish requests if contact is already subscribed */
|
||||||
|
if (contact.get_subscribe_state() == Tp.SubscriptionState.YES ||
|
||||||
|
contact.get_subscribe_state() == Tp.SubscriptionState.ASK) {
|
||||||
|
|
||||||
|
contact.authorize_publication_async(function(src, result) {
|
||||||
|
src.authorize_publication_finish(result)});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display notification to ask user to accept/reject request */
|
||||||
|
let source = this._ensureSubscriptionSource();
|
||||||
|
Main.messageTray.add(source);
|
||||||
|
|
||||||
|
let notif = new SubscriptionRequestNotification(source, contact);
|
||||||
|
source.notify(notif);
|
||||||
|
},
|
||||||
|
|
||||||
|
_ensureSubscriptionSource: function() {
|
||||||
|
if (this._subscriptionSource == null) {
|
||||||
|
this._subscriptionSource = new MultiNotificationSource(
|
||||||
|
_("Subscription request"), 'gtk-dialog-question');
|
||||||
|
this._subscriptionSource.connect('destroy', Lang.bind(this, function () {
|
||||||
|
this._subscriptionSource = null;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._subscriptionSource;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1118,3 +1211,137 @@ FileTransferNotification.prototype = {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A notification source that can embed multiple notifications
|
||||||
|
function MultiNotificationSource(title, icon) {
|
||||||
|
this._init(title, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiNotificationSource.prototype = {
|
||||||
|
__proto__: MessageTray.Source.prototype,
|
||||||
|
|
||||||
|
_init: function(title, icon) {
|
||||||
|
MessageTray.Source.prototype._init.call(this, title);
|
||||||
|
|
||||||
|
this._icon = icon;
|
||||||
|
this._setSummaryIcon(this.createNotificationIcon());
|
||||||
|
this._nbNotifications = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
notify: function(notification) {
|
||||||
|
MessageTray.Source.prototype.notify.call(this, notification);
|
||||||
|
|
||||||
|
this._nbNotifications += 1;
|
||||||
|
|
||||||
|
// Display the source while there is at least one notification
|
||||||
|
notification.connect('destroy', Lang.bind(this, function () {
|
||||||
|
this._nbNotifications -= 1;
|
||||||
|
|
||||||
|
if (this._nbNotifications == 0)
|
||||||
|
this.destroy();
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
createNotificationIcon: function() {
|
||||||
|
return new St.Icon({ gicon: Shell.util_icon_from_string(this._icon),
|
||||||
|
icon_type: St.IconType.FULLCOLOR,
|
||||||
|
icon_size: this.ICON_SIZE });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Subscription request
|
||||||
|
function SubscriptionRequestNotification(source, contact) {
|
||||||
|
this._init(source, contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubscriptionRequestNotification.prototype = {
|
||||||
|
__proto__: MessageTray.Notification.prototype,
|
||||||
|
|
||||||
|
_init: function(source, contact) {
|
||||||
|
MessageTray.Notification.prototype._init.call(this, source,
|
||||||
|
/* To translators: The parameter is the contact's alias */
|
||||||
|
_("%s would like permission to see when you are online").format(contact.get_alias()),
|
||||||
|
null, { customContent: true });
|
||||||
|
|
||||||
|
this._contact = contact;
|
||||||
|
this._connection = contact.get_connection();
|
||||||
|
|
||||||
|
let layout = new St.BoxLayout({ vertical: false });
|
||||||
|
|
||||||
|
// Display avatar
|
||||||
|
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
||||||
|
iconBox._size = 48;
|
||||||
|
|
||||||
|
let textureCache = St.TextureCache.get_default();
|
||||||
|
let file = contact.get_avatar_file();
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
let uri = file.get_uri();
|
||||||
|
iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
iconBox.child = new St.Icon({ icon_name: 'avatar-default',
|
||||||
|
icon_type: St.IconType.FULLCOLOR,
|
||||||
|
icon_size: iconBox._size });
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.add(iconBox);
|
||||||
|
|
||||||
|
// subscription request message
|
||||||
|
let label = new St.Label({ style_class: 'subscription-message',
|
||||||
|
text: contact.get_publish_request() });
|
||||||
|
|
||||||
|
layout.add(label);
|
||||||
|
|
||||||
|
this.addActor(layout);
|
||||||
|
|
||||||
|
this.addButton('decline', _("Decline"));
|
||||||
|
this.addButton('accept', _("Accept"));
|
||||||
|
|
||||||
|
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||||
|
switch (action) {
|
||||||
|
case 'decline':
|
||||||
|
contact.remove_async(function(src, result) {
|
||||||
|
src.remove_finish(result)});
|
||||||
|
break;
|
||||||
|
case 'accept':
|
||||||
|
// Authorize the contact and request to see his status as well
|
||||||
|
contact.authorize_publication_async(function(src, result) {
|
||||||
|
src.authorize_publication_finish(result)});
|
||||||
|
|
||||||
|
contact.request_subscription_async('', function(src, result) {
|
||||||
|
src.request_subscription_finish(result)});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rely on _subscriptionStatesChangedCb to destroy the
|
||||||
|
// notification
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._changedId = contact.connect('subscription-states-changed',
|
||||||
|
Lang.bind(this, this._subscriptionStatesChangedCb));
|
||||||
|
this._invalidatedId = this._connection.connect('invalidated',
|
||||||
|
Lang.bind(this, this.destroy));
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
if (this._changedId != 0) {
|
||||||
|
this._contact.disconnect(this._changedId);
|
||||||
|
this._changedId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._invalidatedId != 0) {
|
||||||
|
this._connection.disconnect(this._invalidatedId);
|
||||||
|
this._invalidatedId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageTray.Notification.prototype.destroy.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_subscriptionStatesChangedCb: function(contact, subscribe, publish, msg) {
|
||||||
|
// Destroy the notification if the subscription request has been
|
||||||
|
// answered
|
||||||
|
if (publish != Tp.SubscriptionState.ASK)
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -19,6 +19,10 @@ struct _ShellTpClientPrivate
|
|||||||
ShellTpClientHandleChannelsImpl handle_channels_impl;
|
ShellTpClientHandleChannelsImpl handle_channels_impl;
|
||||||
gpointer user_data_handle_channels;
|
gpointer user_data_handle_channels;
|
||||||
GDestroyNotify destroy_handle_channels;
|
GDestroyNotify destroy_handle_channels;
|
||||||
|
|
||||||
|
ShellTpClientContactListChangedImpl contact_list_changed_impl;
|
||||||
|
gpointer user_data_contact_list_changed;
|
||||||
|
GDestroyNotify destroy_contact_list_changed;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,6 +81,16 @@ struct _ShellTpClientPrivate
|
|||||||
* Signature of the implementation of the HandleChannels method.
|
* Signature of the implementation of the HandleChannels method.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShellTpClientContactListChangedImpl:
|
||||||
|
* @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared
|
||||||
|
* if possible
|
||||||
|
* @added: (element-type TelepathyGLib.Contact): a #GPtrArray of added #TpContact
|
||||||
|
* @removed: (element-type TelepathyGLib.Contact): a #GPtrArray of removed #TpContact
|
||||||
|
*
|
||||||
|
* Signature of the implementation of the ContactListChanged method.
|
||||||
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
shell_tp_client_init (ShellTpClient *self)
|
shell_tp_client_init (ShellTpClient *self)
|
||||||
{
|
{
|
||||||
@ -220,6 +234,13 @@ shell_tp_client_dispose (GObject *object)
|
|||||||
self->priv->user_data_handle_channels = NULL;
|
self->priv->user_data_handle_channels = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self->priv->destroy_contact_list_changed != NULL)
|
||||||
|
{
|
||||||
|
self->priv->destroy_contact_list_changed (self->priv->user_data_contact_list_changed);
|
||||||
|
self->priv->destroy_contact_list_changed = NULL;
|
||||||
|
self->priv->user_data_contact_list_changed = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (dispose != NULL)
|
if (dispose != NULL)
|
||||||
dispose (object);
|
dispose (object);
|
||||||
}
|
}
|
||||||
@ -278,6 +299,43 @@ shell_tp_client_set_handle_channels_func (ShellTpClient *self,
|
|||||||
self->priv->destroy_handle_channels = destroy;
|
self->priv->destroy_handle_channels = destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
shell_tp_client_set_contact_list_changed_func (ShellTpClient *self,
|
||||||
|
ShellTpClientContactListChangedImpl contact_list_changed_impl,
|
||||||
|
gpointer user_data,
|
||||||
|
GDestroyNotify destroy)
|
||||||
|
{
|
||||||
|
g_assert (self->priv->contact_list_changed_impl == NULL);
|
||||||
|
|
||||||
|
self->priv->contact_list_changed_impl = contact_list_changed_impl;
|
||||||
|
self->priv->user_data_handle_channels = user_data;
|
||||||
|
self->priv->destroy_handle_channels = destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_contact_list_changed (TpConnection *conn,
|
||||||
|
GPtrArray *added,
|
||||||
|
GPtrArray *removed,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
ShellTpClient *self = (ShellTpClient *) user_data;
|
||||||
|
|
||||||
|
g_assert (self->priv->contact_list_changed_impl != NULL);
|
||||||
|
|
||||||
|
self->priv->contact_list_changed_impl (conn,
|
||||||
|
added, removed,
|
||||||
|
self->priv->user_data_contact_list_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
|
||||||
|
TpConnection *conn)
|
||||||
|
{
|
||||||
|
g_signal_connect (conn, "contact-list-changed",
|
||||||
|
G_CALLBACK (on_contact_list_changed),
|
||||||
|
self);
|
||||||
|
}
|
||||||
|
|
||||||
/* Telepathy utility functions */
|
/* Telepathy utility functions */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,6 +85,20 @@ void shell_tp_client_set_handle_channels_func (ShellTpClient *self,
|
|||||||
gpointer user_data,
|
gpointer user_data,
|
||||||
GDestroyNotify destroy);
|
GDestroyNotify destroy);
|
||||||
|
|
||||||
|
typedef void (*ShellTpClientContactListChangedImpl) (
|
||||||
|
TpConnection *connection,
|
||||||
|
GPtrArray *added,
|
||||||
|
GPtrArray *removed,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
void shell_tp_client_set_contact_list_changed_func (ShellTpClient *self,
|
||||||
|
ShellTpClientContactListChangedImpl contact_list_changed_impl,
|
||||||
|
gpointer user_data,
|
||||||
|
GDestroyNotify destroy);
|
||||||
|
|
||||||
|
void shell_tp_client_grab_contact_list_changed (ShellTpClient *self,
|
||||||
|
TpConnection *conn);
|
||||||
|
|
||||||
/* Telepathy utility functions */
|
/* Telepathy utility functions */
|
||||||
typedef void (*ShellGetTpContactCb) (TpConnection *connection,
|
typedef void (*ShellGetTpContactCb) (TpConnection *connection,
|
||||||
GList *contacts,
|
GList *contacts,
|
||||||
|
Loading…
Reference in New Issue
Block a user