Files
gajim-plugins/clients_icons/clients_icons.py
2018-04-04 20:04:36 +02:00

608 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
##
from gi.repository import Gtk
from gi.repository import GdkPixbuf
import os
import logging
from clients_icons.clients import CLIENTS, LIBPURPLE_CLIENTS
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log_calls
from gajim.common import ged
from gajim.common import app
from gajim.common import caps_cache
log = logging.getLogger('gajim.plugin_system.clients_icons')
class ClientsIconsPlugin(GajimPlugin):
@log_calls('ClientsIconsPlugin')
def init(self):
self.description = _('Shows client icons in roster'
' and in groupchats.')
self.pos_list = [_('after statusicon'), _('before avatar')]
self.events_handlers = {
'caps-presence-received': (ged.POSTGUI,
self.caps_presence_received),
'gc-presence-received': (ged.POSTGUI,
self.gc_presence_received),
'caps-disco-received': (ged.POSTGUI,
self.caps_disco_received), }
self.gui_extension_points = {
'groupchat_control': (self.connect_with_groupchat_control,
self.disconnect_from_groupchat_control),
'roster_draw_contact': (self.connect_with_roster_draw_contact, None),
'roster_tooltip_populate': (self.connect_with_roster_tooltip_populate, None),
'gc_tooltip_populate': (self.connect_with_gc_tooltip_populate, None),
}
self.config_default_values = {
'show_in_roster': (True, ''),
'show_in_groupchats': (True, ''),
'show_in_tooltip': (True, ''),
'show_unknown_icon': (True, ''),
'pos_in_list': (0, ''),
'show_facebook': (True, ''),}
self.config_dialog = ClientsIconsPluginConfigDialog(self)
icon_path = os.path.join(self.local_file_path('icons'), 'unknown.png')
self.default_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
icon_path, 16, 16)
self.icon_cache = {}
@log_calls('ClientsIconsPlugin')
def get_client_name(self, contact, client_name):
identities = contact.client_caps.get_cache_lookup_strategy()(
caps_cache.capscache).identities
if identities:
log.debug('get_client_name, identities: %s', str(identities))
for entry in identities:
if entry['category'] == 'client':
client_name = entry.get('name', client_name)
break
return client_name
def get_client_icon_by_name(self, client_name):
client_icon = None
name_splits = client_name.split()
name_splits = reversed([" ".join(name_splits[:(i+1)]) for i in range(len(name_splits))])
for name in name_splits:
if not client_icon:
log.debug("get_client_icon_by_name, searching for name fragment '%s'..." % name)
client_icon = CLIENTS.get(name, (None,))[0]
if client_icon:
break
return client_icon
@staticmethod
def is_groupchat(contact):
if hasattr(contact, 'is_groupchat'):
return contact.is_groupchat()
return False
@log_calls('ClientsIconsPlugin')
def add_tooltip_row(self, tooltip, contact, tooltip_grid):
caps = contact.client_caps._node
log.debug('connect_with_gc_tooltip_populate, caps: %s', caps)
caps_image, client_name = self.get_icon(caps, contact)
client_name = self.get_client_name(contact, client_name)
caps_image.set_halign(Gtk.PositionType.RIGHT)
log.debug('connect_with_gc_tooltip_populate, client_name: %s', client_name)
# fill clients table
self.table = Gtk.Grid()
self.table.set_name('client_icons_grid')
self.table.set_property('column-spacing', 5)
self.table.attach(caps_image, 1, 1, 1, 1)
label_name = Gtk.Label()
label_name.set_halign(Gtk.PositionType.RIGHT)
label_name.set_markup(client_name)
self.table.attach(label_name, 2, 1, 1, 1)
self.table.show_all()
# set label
label = Gtk.Label()
label.set_name('client_icons_label')
label.set_halign(Gtk.PositionType.RIGHT)
label.set_markup(_('Client:'))
label.show()
# set client table to tooltip
tooltip_grid.insert_next_to(tooltip.resource_label,
Gtk.PositionType.BOTTOM)
tooltip_grid.attach_next_to(label, tooltip.resource_label,
Gtk.PositionType.BOTTOM, 1, 1)
tooltip_grid.attach_next_to(self.table, label,
Gtk.PositionType.RIGHT, 1, 1)
@log_calls('ClientsIconsPlugin')
def connect_with_gc_tooltip_populate(self, tooltip, contact, tooltip_grid):
if not self.config['show_in_tooltip']:
return
# Check if clients info already attached to tooltip
has_attached = False
for child in tooltip_grid.get_children():
if child.get_name() == 'client_icons_grid':
caps = contact.client_caps._node
caps_image, client_name = self.get_icon(caps, contact)
child.remove(child.get_child_at(1, 1))
child.attach(caps_image, 1, 1, 1, 1)
child.get_child_at(2, 1).set_markup(client_name)
child.show_all()
if child.get_name() == 'client_icons_label':
child.show()
has_attached = True
if not has_attached:
self.add_tooltip_row(tooltip, contact, tooltip_grid)
@log_calls('ClientsIconsPlugin')
def connect_with_roster_tooltip_populate(self, tooltip, contacts, tooltip_grid):
if not self.config['show_in_tooltip']:
return
if len(contacts) == 1 and contacts[0].jid in app.get_our_jids():
return
if self.is_groupchat(contacts[0]):
return
# put contacts in dict, where key is priority
num_resources = 0
contacts_dict = {}
for contact in contacts:
if contact.resource:
num_resources += 1
if contact.priority in contacts_dict:
contacts_dict[contact.priority].append(contact)
else:
contacts_dict[contact.priority] = [contact]
contact_keys = sorted(contacts_dict.keys())
contact_keys.reverse()
# fill clients table
self.table = Gtk.Grid()
self.table.insert_row(0)
self.table.insert_row(0)
self.table.insert_column(0)
self.table.set_property('column-spacing', 2)
vcard_current_row = 0
for priority in contact_keys:
for acontact in contacts_dict[priority]:
caps = acontact.client_caps._node
caps_image, client_name = self.get_icon(caps, acontact)
client_name = self.get_client_name(acontact, client_name)
caps_image.set_alignment(0, 0)
self.table.attach(caps_image, 1, vcard_current_row, 1, 1)
label = Gtk.Label()
label.set_alignment(0, 0)
label.set_markup(client_name)
self.table.attach(label, 2, vcard_current_row, 1, 1)
vcard_current_row += 1
self.table.show_all()
# set label
label = Gtk.Label()
label.set_alignment(0, 0)
if num_resources > 1:
label.set_markup(_('Clients:'))
else:
if contact.show == 'offline':
return
label.set_markup(_('Client:'))
label.show()
# set clients table to tooltip
tooltip_grid.insert_next_to(tooltip.resource_label,
Gtk.PositionType.BOTTOM)
tooltip_grid.attach_next_to(label, tooltip.resource_label,
Gtk.PositionType.BOTTOM, 1, 1)
tooltip_grid.attach_next_to(self.table, label,
Gtk.PositionType.RIGHT, 1, 1)
def get_icon(self, caps, contact):
client_name = _('Unknown')
caps_ = None
if caps:
# libpurple returns pidgin.im/ only, we have to look for ressource name
if 'pidgin.im/' in caps:
caps = 'libpurple'
for client in LIBPURPLE_CLIENTS:
if client in contact.resource.lower():
caps = LIBPURPLE_CLIENTS[client]
if 'sleekxmpp.com'in caps:
caps = 'httр://sleekxmpp.com/ver/1.1.11'
caps_from_jid = self.check_jid(contact.jid)
if caps_from_jid:
caps = caps_from_jid
caps_ = caps.split('#')[0].split()
client_name = self.get_client_name(contact, client_name)
client_icon = self.get_client_icon_by_name(client_name)
if caps_ and not client_icon:
client_icon = CLIENTS.get(caps_[0].split()[0], (None,))[0]
client_name = CLIENTS.get(caps_[0].split()[0], ('', _('Unknown')))[1]
if not client_icon:
return Gtk.Image.new_from_pixbuf(self.default_pixbuf), _('Unknown')
else:
icon_path = os.path.join(self.local_file_path('icons'), client_icon)
if icon_path in self.icon_cache:
return Gtk.Image.new_from_pixbuf(self.icon_cache[icon_path]), \
client_name
else:
pb = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, 16, 16)
self.icon_cache[icon_path] = pb
return Gtk.Image.new_from_pixbuf(pb), client_name
def check_jid(self, jid):
caps = None
if 'facebook.com' in jid and self.config['show_facebook']:
caps = 'facebook.com'
elif '@vk.com' in jid and self.config['show_facebook']:
caps = 'vk.com'
elif jid == 'juick@juick.com':
caps = 'http://juick.com/caps'
elif jid == 'psto@psto.net':
caps = 'psto@psto.net'
elif jid == 'rss@isida-bot.com':
caps = 'rss@isida-bot.com'
return caps
@log_calls('ClientsIconsPlugin')
def connect_with_roster_draw_contact(self, roster, jid, account, contact):
if not self.active:
return
if not self.config['show_in_roster']:
return
if self.is_groupchat(contact):
return
child_iters = roster._get_contact_iter(jid, account, contact, roster.model)
if not child_iters:
return
for iter_ in child_iters:
if roster.model[iter_][self.renderer_num] is None:
caps = contact.client_caps._node
if not caps:
caps = self.check_jid(jid)
self.set_icon(roster.model, iter_, self.renderer_num, caps, contact)
@log_calls('ClientsIconsPlugin')
def connect_with_groupchat_control(self, chat_control):
chat_control.nb_ext_renderers += 1
chat_control.columns += [GdkPixbuf.Pixbuf]
self.groupchats_tree_is_transformed = True
self.chat_control = chat_control
col = Gtk.TreeViewColumn()
self.muc_renderer_num = 4 + chat_control.nb_ext_renderers
client_icon_rend = (
'client_icon', Gtk.CellRendererPixbuf(), False,
'pixbuf', self.muc_renderer_num,
self.tree_cell_data_func, chat_control)
# remove old column
chat_control.list_treeview.remove_column(
chat_control.list_treeview.get_column(0))
# add new renderer in renderers list
position_list = ['name', 'avatar']
position = position_list[self.config['pos_in_list']]
for renderer in chat_control.renderers_list:
if renderer[0] == position:
break
num = chat_control.renderers_list.index(renderer)
chat_control.renderers_list.insert(num, client_icon_rend)
# fill and append column
chat_control.fill_column(col)
chat_control.list_treeview.insert_column(col, 0)
chat_control.model = Gtk.TreeStore(*chat_control.columns)
chat_control.model.set_sort_func(1, chat_control.tree_compare_iters)
chat_control.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
chat_control.list_treeview.set_model(chat_control.model)
# draw roster
for nick in app.contacts.get_nick_list(
chat_control.account, chat_control.room_jid):
gc_contact = app.contacts.get_gc_contact(
chat_control.account, chat_control.room_jid, nick)
iter_ = chat_control.add_contact_to_roster(
nick, gc_contact.show, gc_contact.role,
gc_contact.affiliation, gc_contact.status, gc_contact.jid)
if not self.config['show_in_groupchats']:
log.debug("not showing in groupchats...")
continue
caps = gc_contact.client_caps._node
log.debug("caps: %s" % str(caps))
self.set_icon(
chat_control.model, iter_,
self.muc_renderer_num, caps, gc_contact)
chat_control.draw_all_roles()
# Recalculate column width for ellipsizin
chat_control.list_treeview.columns_autosize()
@log_calls('ClientsIconsPlugin')
def disconnect_from_groupchat_control(self, gc_control):
gc_control.nb_ext_renderers -= 1
col = gc_control.list_treeview.get_column(0)
gc_control.list_treeview.remove_column(col)
col = Gtk.TreeViewColumn()
for renderer in gc_control.renderers_list:
if renderer[0] == 'client_icon':
gc_control.renderers_list.remove(renderer)
break
gc_control.fill_column(col)
gc_control.list_treeview.insert_column(col, 0)
gc_control.columns = gc_control.columns[:self.muc_renderer_num] + \
gc_control.columns[self.muc_renderer_num + 1:]
gc_control.model = Gtk.TreeStore(*gc_control.columns)
gc_control.model.set_sort_func(1, gc_control.tree_compare_iters)
gc_control.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
gc_control.list_treeview.set_model(gc_control.model)
gc_control.draw_roster()
@log_calls('ClientsIconsPlugin')
def activate(self):
self.active = None
roster = app.interface.roster
col = Gtk.TreeViewColumn()
roster.nb_ext_renderers += 1
self.renderer_num = 11 + roster.nb_ext_renderers
self.renderer = Gtk.CellRendererPixbuf()
client_icon_rend = (
'client_icon', self.renderer, False,
'pixbuf', self.renderer_num,
roster._fill_pep_pixbuf_renderer, self.renderer_num)
# remove old column
roster.tree.remove_column(roster.tree.get_column(0))
# add new renderer in renderers list
position_list = ['name', 'avatar']
position = position_list[self.config['pos_in_list']]
for renderer in roster.renderers_list:
if renderer[0] == position:
break
num = roster.renderers_list.index(renderer)
roster.renderers_list.insert(num, client_icon_rend)
# fill and append column
roster.fill_column(col)
roster.tree.insert_column(col, 0)
# redraw roster
roster.columns += [GdkPixbuf.Pixbuf]
self.active = True
roster.setup_and_draw_roster()
@log_calls('ClientsIconsPlugin')
def deactivate(self):
self.active = None
roster = app.interface.roster
roster.nb_ext_renderers -= 1
col = roster.tree.get_column(0)
roster.tree.remove_column(col)
col = Gtk.TreeViewColumn()
for renderer in roster.renderers_list:
if renderer[0] == 'client_icon':
roster.renderers_list.remove(renderer)
break
roster.fill_column(col)
roster.tree.insert_column(col, 0)
roster.columns = roster.columns[:self.renderer_num] + roster.columns[
self.renderer_num + 1:]
roster.setup_and_draw_roster()
def caps_disco_received(self, iq_obj):
log.debug("caps disco received...")
if not self.config['show_in_roster']:
return
roster = app.interface.roster
contact = app.contacts.get_contact_from_full_jid(
iq_obj.conn.name, iq_obj.jid)
if self.is_groupchat(contact):
return
if contact is None:
room_jid, nick = app.get_room_and_nick_from_fjid(iq_obj.fjid)
contact = app.contacts.get_gc_contact(iq_obj.conn.name, room_jid, nick)
if contact:
gc_control = app.interface.msg_win_mgr.get_gc_control(
iq_obj.jid, iq_obj.conn.name)
iter_ = gc_control.get_contact_iter(nick)
self.set_icon(gc_control.model, iter_,
self.muc_renderer_num, None, contact)
return
if not contact:
return
child_iters = roster._get_contact_iter(
iq_obj.jid, iq_obj.conn.name, contact, roster.model)
if not child_iters:
return
for iter_ in child_iters:
caps = contact.client_caps._node
caps_ = self.check_jid(iq_obj.jid)
if caps_:
caps = caps_
self.set_icon(roster.model, iter_, self.renderer_num, caps, contact)
def caps_presence_received(self, iq_obj):
log.debug("caps presence received...")
if not self.config['show_in_roster']:
return
roster = app.interface.roster
contact = app.contacts.get_contact_with_highest_priority(
iq_obj.conn.name, iq_obj.jid)
if not contact or self.is_groupchat(contact):
return
if iq_obj.resource == 'local':
# zeroconf
return
iters = roster._get_contact_iter(iq_obj.jid, iq_obj.conn.name, contact,
roster.model)
iter_ = iters[0]
if contact.show == 'error':
self.set_icon(roster.model, iter_, self.renderer_num, None, contact)
return
# higest contact changed
if roster.model[iter_][self.renderer_num] is not None:
caps = contact.client_caps._node
if caps:
log.debug('caps_presence_received, caps: %s', caps)
self.set_icon(roster.model, iter_, self.renderer_num, caps, contact)
return
caps = None
tag = iq_obj.stanza.getTags('c')
if tag:
caps = tag[0].getAttr('node')
if caps:
if 'pidgin.im/' in caps:
caps = 'libpurple'
for client in LIBPURPLE_CLIENTS:
if client in contact.resource.lower():
caps = LIBPURPLE_CLIENTS[client]
if 'sleekxmpp.com'in caps:
caps = 'httр://sleekxmpp.com/ver/1.1.11'
caps_from_jid = self.check_jid(iq_obj.jid)
if caps_from_jid:
caps = caps_from_jid
for iter_ in iters:
self.set_icon(roster.model, iter_, self.renderer_num, caps, contact)
def gc_presence_received(self, iq_obj):
log.debug("gc presence received...")
if not self.config['show_in_groupchats']:
return
contact = app.contacts.get_gc_contact(
iq_obj.conn.name, iq_obj.presence_obj.jid, iq_obj.nick)
if not contact:
return
caps = None
tag = iq_obj.stanza.getTags('c')
if tag:
caps = tag[0].getAttr('node')
if caps:
log.debug('gc_presence_received, caps: %s', caps)
if 'pidgin.im/' in caps:
caps = 'libpurple'
if 'sleekxmpp.com' in caps:
caps = 'httр://sleekxmpp.com/ver/1.1.11'
iter_ = iq_obj.gc_control.get_contact_iter(iq_obj.nick)
model = iq_obj.gc_control.model
if model[iter_][self.muc_renderer_num] is not None:
return
self.set_icon(model, iter_, self.muc_renderer_num, caps, contact)
def set_icon(self, model, iter_, pos, caps, contact):
client_icon = self.get_client_icon_by_name(
self.get_client_name(contact, _('Unknown')))
if caps and not client_icon:
caps_ = caps.split('#')[0].split()
if caps_:
log.debug('set_icon, caps_: %s', caps_)
client_icon = CLIENTS.get(caps_[0].split()[0], (None,))[0]
if not client_icon:
if self.config['show_unknown_icon']:
model[iter_][pos] = self.default_pixbuf
else:
icon_path = os.path.join(self.local_file_path('icons'), client_icon)
if icon_path in self.icon_cache:
model[iter_][pos] = self.icon_cache[icon_path]
else:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, 16, 16)
model[iter_][pos] = pixbuf
self.icon_cache[icon_path] = pixbuf
def tree_cell_data_func(self, column, renderer, model, iter_, control):
if not model.iter_parent(iter_):
renderer.set_property('visible', False)
return
elif model[iter_][self.muc_renderer_num]:
renderer.set_property('visible', True)
contact = app.contacts.get_gc_contact(
control.account, control.room_jid, model[iter_][1])
if not contact:
return
bgcolor = app.config.get_per('themes', app.config.get(
'roster_theme'), 'contactbgcolor')
if bgcolor:
renderer.set_property('cell-background', bgcolor)
else:
renderer.set_property('cell-background', None)
renderer.set_property('width', 16)
class ClientsIconsPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.Gtk_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = Gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.Gtk_BUILDER_FILE_PATH, ['vbox1'])
vbox = self.xml.get_object('vbox1')
self.get_child().pack_start(vbox, True, True, 0)
self.combo = self.xml.get_object('combobox1')
self.liststore = Gtk.ListStore(str)
self.combo.set_model(self.liststore)
cellrenderer = Gtk.CellRendererText()
self.combo.pack_start(cellrenderer, True)
self.combo.add_attribute(cellrenderer, 'text', 0)
for item in self.plugin.pos_list:
self.liststore.append((item,))
self.combo.set_active(self.plugin.config['pos_in_list'])
self.xml.get_object('show_in_roster').set_active(
self.plugin.config['show_in_roster'])
self.xml.get_object('show_in_groupchats').set_active(
self.plugin.config['show_in_groupchats'])
self.xml.get_object('show_unknown_icon').set_active(
self.plugin.config['show_unknown_icon'])
self.xml.get_object('show_facebook').set_active(
self.plugin.config['show_facebook'])
self.xml.get_object('show_in_tooltip').set_active(
self.plugin.config['show_in_tooltip'])
self.xml.connect_signals(self)
def redraw_all(self):
self.plugin.deactivate()
self.plugin.activate()
for gc_control in app.interface.msg_win_mgr.get_controls('gc'):
self.plugin.disconnect_from_groupchat_control(gc_control)
for gc_control in app.interface.msg_win_mgr.get_controls('gc'):
self.plugin.connect_with_groupchat_control(gc_control)
def on_show_in_roster_toggled(self, widget):
self.plugin.config['show_in_roster'] = widget.get_active()
self.plugin.deactivate()
self.plugin.activate()
def on_show_in_tooltip_toggled(self, widget):
self.plugin.config['show_in_tooltip'] = widget.get_active()
def on_show_in_groupchats_toggled(self, widget):
self.plugin.config['show_in_groupchats'] = widget.get_active()
for gc_control in app.interface.msg_win_mgr.get_controls('gc'):
self.plugin.disconnect_from_groupchat_control(gc_control)
for gc_control in app.interface.msg_win_mgr.get_controls('gc'):
self.plugin.connect_with_groupchat_control(gc_control)
def on_show_unknown_icon_toggled(self, widget):
self.plugin.config['show_unknown_icon'] = widget.get_active()
self.redraw_all()
def on_show_facebook_toggled(self, widget):
self.plugin.config['show_facebook'] = widget.get_active()
self.redraw_all()
def on_combobox1_changed(self, widget):
self.plugin.config['pos_in_list'] = widget.get_active()
self.redraw_all()