diff --git a/src/gajim.py b/src/gajim.py index b05d34a35..87784d83d 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -67,6 +67,7 @@ import logging log = logging.getLogger('gajim.gajim') import getopt +from common import i18n def parseOpts(): profile = '' @@ -153,6 +154,10 @@ except exceptions.DatabaseMalformed: pritext = _('Database Error') sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH else: + from common import dbus_support + if dbus_support.supported: + from music_track_listener import MusicTrackListener + import dbus from ctypes import CDLL from ctypes.util import find_library @@ -213,18 +218,48 @@ if pritext: del pritext +import gtkexcepthook + import gobject if not hasattr(gobject, 'timeout_add_seconds'): def timeout_add_seconds_fake(time_sec, *args): return gobject.timeout_add(time_sec * 1000, *args) gobject.timeout_add_seconds = timeout_add_seconds_fake +import re import signal +import time +import math + import gtkgui_helpers +import notify +import message_control + +from chat_control import ChatControlBase +from chat_control import ChatControl +from groupchat_control import GroupchatControl +from groupchat_control import PrivateChatControl + +from atom_window import AtomWindow +from session import ChatControlSession + +import common.sleepy + +from common.xmpp import idlequeue +from common.zeroconf import connection_zeroconf +from common import resolver +from common import proxy65_manager +from common import socks5 +from common import helpers +from common import optparser +from common import dataforms +from common import passwords +from common import pep gajimpaths = common.configpaths.gajimpaths pid_filename = gajimpaths['PID_FILE'] +config_filename = gajimpaths['CONFIG_FILE'] import traceback import errno @@ -359,8 +394,3258 @@ def on_exit(): import atexit atexit.register(on_exit) +parser = optparser.OptionsParser(config_filename) -from interface import Interface +import roster_window +import profile_window +import config +from threading import Thread + + +class PassphraseRequest: + def __init__(self, keyid): + self.keyid = keyid + self.callbacks = [] + self.dialog_created = False + self.dialog = None + self.completed = False + + def interrupt(self): + self.dialog.window.destroy() + self.callbacks = [] + + def run_callback(self, account, callback): + gajim.connections[account].gpg_passphrase(self.passphrase) + callback() + + def add_callback(self, account, cb): + if self.completed: + self.run_callback(account, cb) + else: + self.callbacks.append((account, cb)) + if not self.dialog_created: + self.create_dialog(account) + + def complete(self, passphrase): + self.passphrase = passphrase + self.completed = True + if passphrase is not None: + gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, + self.keyid) + for (account, cb) in self.callbacks: + self.run_callback(account, cb) + del self.callbacks + + def create_dialog(self, account): + title = _('Passphrase Required') + second = _('Enter GPG key passphrase for key %(keyid)s (account ' + '%(account)s).') % {'keyid': self.keyid, 'account': account} + + def _cancel(): + # user cancelled, continue without GPG + self.complete(None) + + def _ok(passphrase, checked, count): + result = gajim.connections[account].test_gpg_passphrase(passphrase) + if result == 'ok': + # passphrase is good + self.complete(passphrase) + return + elif result == 'expired': + dialogs.ErrorDialog(_('GPG key expired'), + _('Your GPG key has expied, you will be connected to %s without ' + 'OpenPGP.') % account) + # Don't try to connect with GPG + gajim.connections[account].continue_connect_info[2] = False + self.complete(None) + return + + if count < 3: + # ask again + dialogs.PassphraseDialog(_('Wrong Passphrase'), + _('Please retype your GPG passphrase or press Cancel.'), + ok_handler=(_ok, count + 1), cancel_handler=_cancel) + else: + # user failed 3 times, continue without GPG + self.complete(None) + + self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), + cancel_handler=_cancel) + self.dialog_created = True + + +class ThreadInterface: + def __init__(self, func, func_args, callback, callback_args): + '''Call a function in a thread + + :param func: the function to call in the thread + :param func_args: list or arguments for this function + :param callback: callback to call once function is finished + :param callback_args: list of arguments for this callback + ''' + def thread_function(func, func_args, callback, callback_args): + output = func(*func_args) + gobject.idle_add(callback, output, *callback_args) + Thread(target=thread_function, args=(func, func_args, callback, + callback_args)).start() + +class Interface: + +################################################################################ +### Methods handling events from connection +################################################################################ + + def handle_event_roster(self, account, data): + #('ROSTER', account, array) + # FIXME: Those methods depend to highly on each other + # and the order in which they are called + self.roster.fill_contacts_and_groups_dicts(data, account) + self.roster.add_account_contacts(account) + self.roster.fire_up_unread_messages_events(account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Roster', (account, data)) + + def handle_event_warning(self, unused, data): + #('WARNING', account, (title_text, section_text)) + dialogs.WarningDialog(data[0], data[1]) + + def handle_event_error(self, unused, data): + #('ERROR', account, (title_text, section_text)) + dialogs.ErrorDialog(data[0], data[1]) + + def handle_event_information(self, unused, data): + #('INFORMATION', account, (title_text, section_text)) + dialogs.InformationDialog(data[0], data[1]) + + def handle_event_ask_new_nick(self, account, data): + #('ASK_NEW_NICK', account, (room_jid,)) + room_jid = data[0] + title = _('Unable to join group chat') + prompt = _('Your desired nickname in group chat %s is in use or ' + 'registered by another occupant.\nPlease specify another nickname ' + 'below:') % room_jid + check_text = _('Always use this nickname when there is a conflict') + if 'change_nick_dialog' in self.instances: + self.instances['change_nick_dialog'].add_room(account, room_jid, + prompt) + else: + self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( + account, room_jid, title, prompt) + + def handle_event_http_auth(self, account, data): + #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) + def response(account, iq_obj, answer): + self.dialog.destroy() + gajim.connections[account].build_http_auth_answer(iq_obj, answer) + + def on_yes(is_checked, account, iq_obj): + response(account, iq_obj, 'yes') + + sec_msg = _('Do you accept this request?') + if gajim.get_number_of_connected_accounts() > 1: + sec_msg = _('Do you accept this request on account %s?') % account + if data[4]: + sec_msg = data[4] + '\n' + sec_msg + self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' + '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], + 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), + on_response_no=(response, account, data[3], 'no')) + + def handle_event_error_answer(self, account, array): + #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) + id_, jid_from, errmsg, errcode = array + if unicode(errcode) in ('400', '403', '406') and id_: + # show the error dialog + ft = self.instances['file_transfers'] + sid = id_ + if len(id_) > 3 and id_[2] == '_': + sid = id_[3:] + if sid in ft.files_props['s']: + file_props = ft.files_props['s'][sid] + if unicode(errcode) == '400': + file_props['error'] = -3 + else: + file_props['error'] = -4 + self.handle_event_file_request_error(account, + (jid_from, file_props, errmsg)) + conn = gajim.connections[account] + conn.disconnect_transfer(file_props) + return + elif unicode(errcode) == '404': + conn = gajim.connections[account] + sid = id_ + if len(id_) > 3 and id_[2] == '_': + sid = id_[3:] + if sid in conn.files_props: + file_props = conn.files_props[sid] + self.handle_event_file_send_error(account, + (jid_from, file_props)) + conn.disconnect_transfer(file_props) + return + + ctrl = self.msg_win_mgr.get_control(jid_from, account) + if ctrl and ctrl.type_id == message_control.TYPE_GC: + ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) + + def handle_event_con_type(self, account, con_type): + # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain' + gajim.con_types[account] = con_type + self.roster.draw_account(account) + + def handle_event_connection_lost(self, account, array): + # ('CONNECTION_LOST', account, [title, text]) + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'connection_lost.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + notify.popup(_('Connection Failed'), account, account, + 'connection_failed', path, array[0], array[1]) + + def unblock_signed_in_notifications(self, account): + gajim.block_signed_in_notifications[account] = False + + def handle_event_status(self, account, show): # OUR status + #('STATUS', account, show) + model = self.roster.status_combobox.get_model() + if show in ('offline', 'error'): + for name in self.instances[account]['online_dialog'].keys(): + # .keys() is needed to not have a dictionary length changed during + # iteration error + self.instances[account]['online_dialog'][name].destroy() + del self.instances[account]['online_dialog'][name] + for request in self.gpg_passphrase.values(): + if request: + request.interrupt() + # .keys() is needed because dict changes during loop + for account in self.pass_dialog.keys(): + self.pass_dialog[account].window.destroy() + if show == 'offline': + # sensitivity for this menuitem + if gajim.get_number_of_connected_accounts() == 0: + model[self.roster.status_message_menuitem_iter][3] = False + gajim.block_signed_in_notifications[account] = True + else: + # 30 seconds after we change our status to sth else than offline + # we stop blocking notifications of any kind + # this prevents from getting the roster items as 'just signed in' + # contacts. 30 seconds should be enough time + gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) + # sensitivity for this menuitem + model[self.roster.status_message_menuitem_iter][3] = True + + # Inform all controls for this account of the connection state change + ctrls = self.msg_win_mgr.get_controls() + if account in self.minimized_controls: + # Can not be the case when we remove account + ctrls += self.minimized_controls[account].values() + for ctrl in ctrls: + if ctrl.account == account: + if show == 'offline' or (show == 'invisible' and \ + gajim.connections[account].is_zeroconf): + ctrl.got_disconnected() + else: + # Other code rejoins all GCs, so we don't do it here + if not ctrl.type_id == message_control.TYPE_GC: + ctrl.got_connected() + if ctrl.parent_win: + ctrl.parent_win.redraw_tab(ctrl) + + self.roster.on_status_changed(account, show) + if account in self.show_vcard_when_connect and show not in ('offline', + 'error'): + self.edit_own_details(account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('AccountPresence', (show, account)) + + def edit_own_details(self, account): + jid = gajim.get_jid_from_account(account) + if 'profile' not in self.instances[account]: + self.instances[account]['profile'] = \ + profile_window.ProfileWindow(account) + gajim.connections[account].request_vcard(jid) + + def handle_event_notify(self, account, array): + # 'NOTIFY' (account, (jid, status, status message, resource, + # priority, # keyID, timestamp, contact_nickname)) + # + # Contact changed show + + # FIXME: Drop and rewrite... + + statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', + 'invisible'] + # Ignore invalid show + if array[1] not in statuss: + return + old_show = 0 + new_show = statuss.index(array[1]) + status_message = array[2] + jid = array[0].split('/')[0] + keyID = array[5] + contact_nickname = array[7] + + # Get the proper keyID + keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) + + resource = array[3] + if not resource: + resource = '' + priority = array[4] + if gajim.jid_is_transport(jid): + # It must be an agent + ji = jid.replace('@', '') + else: + ji = jid + + highest = gajim.contacts. \ + get_contact_with_highest_priority(account, jid) + was_highest = (highest and highest.resource == resource) + + conn = gajim.connections[account] + + # Update contact + jid_list = gajim.contacts.get_jid_list(account) + if ji in jid_list or jid == gajim.get_jid_from_account(account): + lcontact = gajim.contacts.get_contacts(account, ji) + contact1 = None + resources = [] + for c in lcontact: + resources.append(c.resource) + if c.resource == resource: + contact1 = c + break + + if contact1: + if contact1.show in statuss: + old_show = statuss.index(contact1.show) + # nick changed + if contact_nickname is not None and \ + contact1.contact_name != contact_nickname: + contact1.contact_name = contact_nickname + self.roster.draw_contact(jid, account) + + if old_show == new_show and contact1.status == status_message and \ + contact1.priority == priority: # no change + return + else: + contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) + if not contact1: + # Presence of another resource of our + # jid + # Create self contact and add to roster + if resource == conn.server_resource: + return + # Ignore offline presence of unknown self resource + if new_show < 2: + return + contact1 = gajim.contacts.create_contact(jid=ji, + name=gajim.nicks[account], groups=['self_contact'], + show=array[1], status=status_message, sub='both', ask='none', + priority=priority, keyID=keyID, resource=resource, + mood=conn.mood, tune=conn.tune, activity=conn.activity) + old_show = 0 + gajim.contacts.add_contact(account, contact1) + lcontact.append(contact1) + elif contact1.show in statuss: + old_show = statuss.index(contact1.show) + if (resources != [''] and (len(lcontact) != 1 or \ + lcontact[0].show != 'offline')) and jid.find('@') > 0: + # Another resource of an existing contact connected + old_show = 0 + contact1 = gajim.contacts.copy_contact(contact1) + lcontact.append(contact1) + contact1.resource = resource + + self.roster.add_contact(contact1.jid, account) + + if contact1.jid.find('@') > 0 and len(lcontact) == 1: + # It's not an agent + if old_show == 0 and new_show > 1: + if not contact1.jid in gajim.newly_added[account]: + gajim.newly_added[account].append(contact1.jid) + if contact1.jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].remove(contact1.jid) + gobject.timeout_add_seconds(5, self.roster.remove_newly_added, + contact1.jid, account) + elif old_show > 1 and new_show == 0 and conn.connected > 1: + if not contact1.jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].append(contact1.jid) + if contact1.jid in gajim.newly_added[account]: + gajim.newly_added[account].remove(contact1.jid) + self.roster.draw_contact(contact1.jid, account) + gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, + contact1.jid, account) + + # unset custom status + if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ + and conn.connected > 1): + if account in self.status_sent_to_users and \ + jid in self.status_sent_to_users[account]: + del self.status_sent_to_users[account][jid] + + contact1.show = array[1] + contact1.status = status_message + contact1.priority = priority + contact1.keyID = keyID + timestamp = array[6] + if timestamp: + contact1.last_status_time = timestamp + elif not gajim.block_signed_in_notifications[account]: + # We're connected since more that 30 seconds + contact1.last_status_time = time.localtime() + contact1.contact_nickname = contact_nickname + + if gajim.jid_is_transport(jid): + # It must be an agent + if ji in jid_list: + # Update existing iter and group counting + self.roster.draw_contact(ji, account) + self.roster.draw_group(_('Transports'), account) + if new_show > 1 and ji in gajim.transport_avatar[account]: + # transport just signed in. + # request avatars + for jid_ in gajim.transport_avatar[account][ji]: + conn.request_vcard(jid_) + # transport just signed in/out, don't show + # popup notifications for 30s + account_ji = account + '/' + ji + gajim.block_signed_in_notifications[account_ji] = True + gobject.timeout_add_seconds(30, + self.unblock_signed_in_notifications, account_ji) + locations = (self.instances, self.instances[account]) + for location in locations: + if 'add_contact' in location: + if old_show == 0 and new_show > 1: + location['add_contact'].transport_signed_in(jid) + break + elif old_show > 1 and new_show == 0: + location['add_contact'].transport_signed_out(jid) + break + elif ji in jid_list: + # It isn't an agent + # reset chatstate if needed: + # (when contact signs out or has errors) + if array[1] in ('offline', 'error'): + contact1.our_chatstate = contact1.chatstate = \ + contact1.composing_xep = None + + # TODO: This causes problems when another + # resource signs off! + conn.remove_transfers_for_contact(contact1) + + # disable encryption, since if any messages are + # lost they'll be not decryptable (note that + # this contradicts XEP-0201 - trying to get that + # in the XEP, though) + + # there won't be any sessions here if the contact terminated + # their sessions before going offline (which we do) + for sess in conn.get_sessions(ji): + if (ji+'/'+resource) != str(sess.jid): + continue + if sess.control: + sess.control.no_autonegotiation = False + if sess.enable_encryption: + sess.terminate_e2e() + conn.delete_session(jid, sess.thread_id) + + self.roster.chg_contact_status(contact1, array[1], status_message, + account) + # Notifications + if old_show < 2 and new_show > 1: + notify.notify('contact_connected', jid, account, status_message) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactPresence', (account, + array)) + + elif old_show > 1 and new_show < 2: + notify.notify('contact_disconnected', jid, account, status_message) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) + # FIXME: stop non active file transfers + # Status change (not connected/disconnected or + # error (<1)) + elif new_show > 1: + notify.notify('status_change', jid, account, [new_show, + status_message]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('ContactStatus', (account, array)) + else: + # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't + # follow the XEP, still the case in 2008. + # It's maybe a GC_NOTIFY (specialy for MSN gc) + self.handle_event_gc_notify(account, (jid, array[1], status_message, + array[3], None, None, None, None, None, [], None, None)) + + highest = gajim.contacts.get_contact_with_highest_priority(account, jid) + is_highest = (highest and highest.resource == resource) + + # disconnect the session from the ctrl if the highest resource has changed + if (was_highest and not is_highest) or (not was_highest and is_highest): + ctrl = self.msg_win_mgr.get_control(jid, account) + + if ctrl: + ctrl.no_autonegotiation = False + ctrl.set_session(None) + ctrl.contact = highest + + def handle_event_msgerror(self, account, array): + #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) + full_jid_with_resource = array[0] + jids = full_jid_with_resource.split('/', 1) + jid = jids[0] + + if array[1] == '503': + # If we get server-not-found error, stop sending chatstates + for contact in gajim.contacts.get_contacts(account, jid): + contact.composing_xep = False + + session = None + if len(array) > 5: + session = array[5] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + if gc_control and gc_control.type_id != message_control.TYPE_GC: + gc_control = None + if gc_control: + if len(jids) > 1: # it's a pm + nick = jids[1] + + if session: + ctrl = session.control + else: + ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) + + if not ctrl: + tv = gc_control.list_treeview + model = tv.get_model() + iter_ = gc_control.get_contact_iter(nick) + if iter_: + show = model[iter_][3] + else: + show = 'offline' + gc_c = gajim.contacts.create_gc_contact(room_jid = jid, + name = nick, show = show) + ctrl = self.new_private_chat(gc_c, account, session) + + ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { + 'code': array[1], 'msg': array[2]}, 'status') + return + + gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { + 'code': array[1], 'msg': array[2]}, 'status') + if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: + gc_control.set_subject(gc_control.subject) + return + + if gajim.jid_is_transport(jid): + jid = jid.replace('@', '') + msg = array[2] + if array[3]: + msg = _('error while sending %(message)s ( %(error)s )') % { + 'message': array[3], 'error': msg} + if session: + session.roster_message(jid, msg, array[4], msg_type='error') + + def handle_event_msgsent(self, account, array): + #('MSGSENT', account, (jid, msg, keyID)) + msg = array[1] + # do not play sound when standalone chatstate message (eg no msg) + if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): + helpers.play_sound('message_sent') + + def handle_event_msgnotsent(self, account, array): + #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) + msg = _('error while sending %(message)s ( %(error)s )') % { + 'message': array[2], 'error': array[1]} + if not array[4]: + # No session. This can happen when sending a message from gajim-remote + log.warn(msg) + return + array[4].roster_message(array[0], msg, array[3], account, + msg_type='error') + + def handle_event_subscribe(self, account, array): + #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Subscribe', (account, array)) + + jid = array[0] + text = array[1] + nick = array[2] + if helpers.allow_popup_window(account) or not self.systray_enabled: + dialogs.SubscriptionRequestWindow(jid, text, account, nick) + return + + self.add_event(account, jid, 'subscription_request', (text, nick)) + + if helpers.allow_showing_notification(account): + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'subscription_request.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + event_type = _('Subscription request') + notify.popup(event_type, jid, account, 'subscription_request', path, + event_type, jid) + + def handle_event_subscribed(self, account, array): + #('SUBSCRIBED', account, (jid, resource)) + jid = array[0] + if jid in gajim.contacts.get_jid_list(account): + c = gajim.contacts.get_first_contact_from_jid(account, jid) + c.resource = array[1] + self.roster.remove_contact_from_groups(c.jid, account, + [_('Not in Roster'), _('Observers')], update=False) + else: + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + name = jid.split('@', 1)[0] + name = name.split('%', 1)[0] + contact1 = gajim.contacts.create_contact(jid=jid, name=name, + groups=[], show='online', status='online', + ask='to', resource=array[1], keyID=keyID) + gajim.contacts.add_contact(account, contact1) + self.roster.add_contact(jid, account) + dialogs.InformationDialog(_('Authorization accepted'), + _('The contact "%s" has authorized you to see his or her status.') + % jid) + if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): + gajim.connections[account].ack_subscribed(jid) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Subscribed', (account, array)) + + def show_unsubscribed_dialog(self, account, contact): + def on_yes(is_checked, list_): + self.roster.on_req_usub(None, list_) + list_ = [(contact, account)] + dialogs.YesNoDialog( + _('Contact "%s" removed subscription from you') % contact.jid, + _('You will always see him or her as offline.\nDo you want to ' + 'remove him or her from your contact list?'), + on_response_yes=(on_yes, list_)) + # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does + # not show deny + + def handle_event_unsubscribed(self, account, jid): + #('UNSUBSCRIBED', account, jid) + gajim.connections[account].ack_unsubscribed(jid) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) + + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + if not contact: + return + + if helpers.allow_popup_window(account) or not self.systray_enabled: + self.show_unsubscribed_dialog(account, contact) + + self.add_event(account, jid, 'unsubscribed', contact) + + if helpers.allow_showing_notification(account): + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'unsubscribed.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + event_type = _('Unsubscribed') + notify.popup(event_type, jid, account, 'unsubscribed', path, + event_type, jid) + + def handle_event_agent_info_error(self, account, agent): + #('AGENT_ERROR_INFO', account, (agent)) + try: + gajim.connections[account].services_cache.agent_info_error(agent) + except AttributeError: + return + + def handle_event_agent_items_error(self, account, agent): + #('AGENT_ERROR_INFO', account, (agent)) + try: + gajim.connections[account].services_cache.agent_items_error(agent) + except AttributeError: + return + + def handle_event_agent_removed(self, account, agent): + # remove transport's contacts from treeview + jid_list = gajim.contacts.get_jid_list(account) + for jid in jid_list: + if jid.endswith('@' + agent): + c = gajim.contacts.get_first_contact_from_jid(account, jid) + gajim.log.debug( + 'Removing contact %s due to unregistered transport %s'\ + % (jid, agent)) + gajim.connections[account].unsubscribe(c.jid) + # Transport contacts can't have 2 resources + if c.jid in gajim.to_be_removed[account]: + # This way we'll really remove it + gajim.to_be_removed[account].remove(c.jid) + self.roster.remove_contact(c.jid, account, backend=True) + + def handle_event_register_agent_info(self, account, array): + # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) + # info in a dataform if is_form is True + if array[2] or 'instructions' in array[1]: + config.ServiceRegistrationWindow(array[0], array[1], account, + array[2]) + else: + dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ + % array[0], _('Check your connection or try again later.')) + + def handle_event_agent_info_items(self, account, array): + #('AGENT_INFO_ITEMS', account, (agent, node, items)) + our_jid = gajim.get_jid_from_account(account) + if 'pep_services' in gajim.interface.instances[account] and \ + array[0] == our_jid: + gajim.interface.instances[account]['pep_services'].items_received( + array[2]) + try: + gajim.connections[account].services_cache.agent_items(array[0], + array[1], array[2]) + except AttributeError: + return + + def handle_event_agent_info_info(self, account, array): + #('AGENT_INFO_INFO', account, (agent, node, identities, features, data)) + try: + gajim.connections[account].services_cache.agent_info(array[0], + array[1], array[2], array[3], array[4]) + except AttributeError: + return + + def handle_event_new_acc_connected(self, account, array): + #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err, + # ssl_cert, ssl_fingerprint)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].new_acc_connected(array[0], + array[1], array[2], array[3], array[4], array[5]) + + def handle_event_new_acc_not_connected(self, account, array): + #('NEW_ACC_NOT_CONNECTED', account, (reason)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].new_acc_not_connected(array) + + def handle_event_acc_ok(self, account, array): + #('ACC_OK', account, (config)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].acc_is_ok(array) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('NewAccount', (account, array)) + + def handle_event_acc_not_ok(self, account, array): + #('ACC_NOT_OK', account, (reason)) + if 'account_creation_wizard' in self.instances: + self.instances['account_creation_wizard'].acc_is_not_ok(array) + + def handle_event_quit(self, p1, p2): + self.roster.quit_gtkgui_interface() + + def handle_event_myvcard(self, account, array): + nick = '' + if 'NICKNAME' in array and array['NICKNAME']: + gajim.nicks[account] = array['NICKNAME'] + elif 'FN' in array and array['FN']: + gajim.nicks[account] = array['FN'] + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.set_values(array) + if account in self.show_vcard_when_connect: + self.show_vcard_when_connect.remove(account) + jid = array['jid'] + if jid in self.instances[account]['infos']: + self.instances[account]['infos'][jid].set_values(array) + + def handle_event_vcard(self, account, vcard): + # ('VCARD', account, data) + '''vcard holds the vcard data''' + jid = vcard['jid'] + resource = vcard.get('resource', '') + fjid = jid + '/' + str(resource) + + # vcard window + win = None + if jid in self.instances[account]['infos']: + win = self.instances[account]['infos'][jid] + elif resource and fjid in self.instances[account]['infos']: + win = self.instances[account]['infos'][fjid] + if win: + win.set_values(vcard) + + # show avatar in chat + ctrl = None + if resource and self.msg_win_mgr.has_window(fjid, account): + win = self.msg_win_mgr.get_window(fjid, account) + ctrl = win.get_control(fjid, account) + elif self.msg_win_mgr.has_window(jid, account): + win = self.msg_win_mgr.get_window(jid, account) + ctrl = win.get_control(jid, account) + + if ctrl and ctrl.type_id != message_control.TYPE_GC: + ctrl.show_avatar() + + # Show avatar in roster or gc_roster + gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_ctrl and \ + jid in self.minimized_controls[account]: + gc_ctrl = self.minimized_controls[account][jid] + if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: + gc_ctrl.draw_avatar(resource) + else: + self.roster.draw_avatar(jid, account) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) + + def handle_event_last_status_time(self, account, array): + # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) + tim = array[2] + if tim < 0: + # Ann error occured + return + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + c = gajim.contacts.get_contact(account, array[0], array[1]) + if c: # c can be none if it's a gc contact + c.last_status_time = time.localtime(time.time() - tim) + if array[3]: + c.status = array[3] + self.roster.draw_contact(c.jid, account) # draw offline status + if win: + win.set_last_status_time() + if self.remote_ctrl: + self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) + + def handle_event_os_info(self, account, array): + #'OS_INFO' (account, (jid, resource, client_info, os_info)) + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + if win: + win.set_os_info(array[1], array[2], array[3]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('OsInfo', (account, array)) + + def handle_event_entity_time(self, account, array): + #'ENTITY_TIME' (account, (jid, resource, time_info)) + win = None + if array[0] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0]] + elif array[0] + '/' + array[1] in self.instances[account]['infos']: + win = self.instances[account]['infos'][array[0] + '/' + array[1]] + if win: + win.set_entity_time(array[1], array[2]) + if self.remote_ctrl: + self.remote_ctrl.raise_signal('EntityTime', (account, array)) + + def handle_event_gc_notify(self, account, array): + #'GC_NOTIFY' (account, (room_jid, show, status, nick, + # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) + nick = array[3] + if not nick: + return + room_jid = array[0] + fjid = room_jid + '/' + nick + show = array[1] + status = array[2] + conn = gajim.connections[account] + + # Get the window and control for the updated status, this may be a + # PrivateChatControl + control = self.msg_win_mgr.get_gc_control(room_jid, account) + + if not control and \ + room_jid in self.minimized_controls[account]: + control = self.minimized_controls[account][room_jid] + + if not control or (control and control.type_id != message_control.TYPE_GC): + return + + control.chg_contact_status(nick, show, status, array[4], array[5], + array[6], array[7], array[8], array[9], array[10], array[11]) + + contact = gajim.contacts.\ + get_contact_with_highest_priority(account, room_jid) + if contact: + self.roster.draw_contact(room_jid, account) + + # print status in chat window and update status/GPG image + ctrl = self.msg_win_mgr.get_control(fjid, account) + if ctrl: + statusCode = array[9] + if '303' in statusCode: + new_nick = array[10] + ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ + % {'nick': nick, 'new_nick': new_nick}, 'status') + gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) + c = gajim.contacts.contact_from_gc_contact(gc_c) + ctrl.gc_contact = gc_c + ctrl.contact = c + if ctrl.session: + # stop e2e + if ctrl.session.enable_encryption: + thread_id = ctrl.session.thread_id + ctrl.session.terminate_e2e() + conn.delete_session(fjid, thread_id) + ctrl.no_autonegotiation = False + ctrl.draw_banner() + old_jid = room_jid + '/' + nick + new_jid = room_jid + '/' + new_nick + self.msg_win_mgr.change_key(old_jid, new_jid, account) + else: + contact = ctrl.contact + contact.show = show + contact.status = status + gc_contact = ctrl.gc_contact + gc_contact.show = show + gc_contact.status = status + uf_show = helpers.get_uf_show(show) + ctrl.print_conversation(_('%(nick)s is now %(status)s') % { + 'nick': nick, 'status': uf_show}, 'status') + if status: + ctrl.print_conversation(' (', 'status', simple=True) + ctrl.print_conversation('%s' % (status), 'status', simple=True) + ctrl.print_conversation(')', 'status', simple=True) + ctrl.parent_win.redraw_tab(ctrl) + ctrl.update_ui() + if self.remote_ctrl: + self.remote_ctrl.raise_signal('GCPresence', (account, array)) + + def handle_event_gc_msg(self, account, array): + # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, + # [status_codes])) + jids = array[0].split('/', 1) + room_jid = jids[0] + + msg = array[1] + + gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) + if not gc_control and \ + room_jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][room_jid] + + if not gc_control: + return + xhtml = array[4] + + if gajim.config.get('ignore_incoming_xhtml'): + xhtml = None + if len(jids) == 1: + # message from server + nick = '' + else: + # message from someone + nick = jids[1] + + gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) + + if self.remote_ctrl: + highlight = gc_control.needs_visual_notification(msg) + array += (highlight,) + self.remote_ctrl.raise_signal('GCMessage', (account, array)) + + def handle_event_gc_subject(self, account, array): + #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) + jids = array[0].split('/', 1) + jid = jids[0] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + + contact = gajim.contacts.\ + get_contact_with_highest_priority(account, jid) + if contact: + contact.status = array[1] + self.roster.draw_contact(jid, account) + + if not gc_control: + return + gc_control.set_subject(array[1]) + # Standard way, the message comes from the occupant who set the subject + text = None + if len(jids) > 1: + text = _('%(jid)s has set the subject to %(subject)s') % { + 'jid': jids[1], 'subject': array[1]} + # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be + # deleted one day. We can receive a subject with a body that contains + # "X has set the subject to Y" ... + elif array[2]: + text = array[2] + if text is not None: + if array[3]: + gc_control.print_old_conversation(text) + else: + gc_control.print_conversation(text) + + def handle_event_gc_config(self, account, array): + #('GC_CONFIG', account, (jid, form)) config is a dict + room_jid = array[0].split('/')[0] + if room_jid in gajim.automatic_rooms[account]: + if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: + # We're converting chat to muc. allow participants to invite + form = dataforms.ExtendForm(node = array[1]) + for f in form.iter_fields(): + if f.var == 'muc#roomconfig_allowinvites': + f.value = True + elif f.var == 'muc#roomconfig_publicroom': + f.value = False + elif f.var == 'muc#roomconfig_membersonly': + f.value = True + elif f.var == 'public_list': + f.value = False + gajim.connections[account].send_gc_config(room_jid, form) + else: + # use default configuration + gajim.connections[account].send_gc_config(room_jid, array[1]) + # invite contacts + # check if it is necessary to add + continue_tag = False + if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: + continue_tag = True + if 'invities' in gajim.automatic_rooms[account][room_jid]: + for jid in gajim.automatic_rooms[account][room_jid]['invities']: + gajim.connections[account].send_invite(room_jid, jid, + continue_tag=continue_tag) + del gajim.automatic_rooms[account][room_jid] + elif room_jid not in self.instances[account]['gc_config']: + self.instances[account]['gc_config'][room_jid] = \ + config.GroupchatConfigWindow(account, room_jid, array[1]) + + def handle_event_gc_config_change(self, account, array): + #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify + # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init + jid = array[0] + statusCode = array[1] + + gc_control = self.msg_win_mgr.get_gc_control(jid, account) + if not gc_control and \ + jid in self.minimized_controls[account]: + gc_control = self.minimized_controls[account][jid] + if not gc_control: + return + + changes = [] + if '100' in statusCode: + # Can be a presence (see chg_contact_status in groupchat_control.py) + changes.append(_('Any occupant is allowed to see your full JID')) + gc_control.is_anonymous = False + if '102' in statusCode: + changes.append(_('Room now shows unavailable member')) + if '103' in statusCode: + changes.append(_('room now does not show unavailable members')) + if '104' in statusCode: + changes.append( + _('A non-privacy-related room configuration change has occurred')) + if '170' in statusCode: + # Can be a presence (see chg_contact_status in groupchat_control.py) + changes.append(_('Room logging is now enabled')) + if '171' in statusCode: + changes.append(_('Room logging is now disabled')) + if '172' in statusCode: + changes.append(_('Room is now non-anonymous')) + gc_control.is_anonymous = False + if '173' in statusCode: + changes.append(_('Room is now semi-anonymous')) + gc_control.is_anonymous = True + if '174' in statusCode: + changes.append(_('Room is now fully-anonymous')) + gc_control.is_anonymous = True + + for change in changes: + gc_control.print_conversation(change) + + def handle_event_gc_affiliation(self, account, array): + #('GC_AFFILIATION', account, (room_jid, users_dict)) + room_jid = array[0] + if room_jid in self.instances[account]['gc_config']: + self.instances[account]['gc_config'][room_jid].\ + affiliation_list_received(array[1]) + + def handle_event_gc_password_required(self, account, array): + #('GC_PASSWORD_REQUIRED', account, (room_jid, nick)) + room_jid = array[0] + nick = array[1] + + def on_ok(text): + gajim.connections[account].join_gc(nick, room_jid, text) + gajim.gc_passwords[room_jid] = text + + def on_cancel(): + # get and destroy window + if room_jid in gajim.interface.minimized_controls[account]: + self.roster.on_disconnect(None, room_jid, account) + else: + win = self.msg_win_mgr.get_window(room_jid, account) + ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) + win.remove_tab(ctrl, 3) + + dlg = dialogs.InputDialog(_('Password Required'), + _('A Password is required to join the room %s. Please type it.') % \ + room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) + dlg.input_entry.set_visibility(False) + + def handle_event_gc_invitation(self, account, array): + #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) + jid = gajim.get_jid_without_resource(array[1]) + room_jid = array[0] + if helpers.allow_popup_window(account) or not self.systray_enabled: + dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3], + array[2], is_continued=array[4]) + return + + self.add_event(account, jid, 'gc-invitation', (room_jid, array[2], + array[3], array[4])) + + if helpers.allow_showing_notification(account): + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'gc_invitation.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(path) + event_type = _('Groupchat Invitation') + notify.popup(event_type, jid, account, 'gc-invitation', path, + event_type, room_jid) + + def forget_gpg_passphrase(self, keyid): + if keyid in self.gpg_passphrase: + del self.gpg_passphrase[keyid] + return False + + def handle_event_bad_passphrase(self, account, array): + #('BAD_PASSPHRASE', account, ()) + use_gpg_agent = gajim.config.get('use_gpg_agent') + sectext = '' + if use_gpg_agent: + sectext = _('You configured Gajim to use GPG agent, but there is no ' + 'GPG agent running or it returned a wrong passphrase.\n') + sectext += _('You are currently connected without your OpenPGP key.') + dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) + else: + path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'warning.png') + notify.popup('warning', account, account, 'warning', path, + _('OpenGPG Passphrase Incorrect'), + _('You are currently connected without your OpenPGP key.')) + keyID = gajim.config.get_per('accounts', account, 'keyid') + self.forget_gpg_passphrase(keyID) + + def handle_event_gpg_password_required(self, account, array): + #('GPG_PASSWORD_REQUIRED', account, (callback,)) + callback = array[0] + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid in self.gpg_passphrase: + request = self.gpg_passphrase[keyid] + else: + request = PassphraseRequest(keyid) + self.gpg_passphrase[keyid] = request + request.add_callback(account, callback) + + def handle_event_gpg_always_trust(self, account, callback): + #('GPG_ALWAYS_TRUST', account, callback) + def on_yes(checked): + if checked: + gajim.connections[account].gpg.always_trust = True + callback(True) + + def on_no(): + callback(False) + + dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' + 'encrypt this chat is not trusted. Do you really want to encrypt this ' + 'message?'), checktext=_('Do _not ask me again'), + on_response_yes=on_yes, on_response_no=on_no) + + def handle_event_password_required(self, account, array): + #('PASSWORD_REQUIRED', account, None) + if account in self.pass_dialog: + return + text = _('Enter your password for account %s') % account + if passwords.USER_HAS_GNOMEKEYRING and \ + not passwords.USER_USES_GNOMEKEYRING: + text += '\n' + _('Gnome Keyring is installed but not \ + correctly started (environment variable probably not \ + correctly set)') + + def on_ok(passphrase, save): + if save: + gajim.config.set_per('accounts', account, 'savepass', True) + passwords.save_password(account, passphrase) + gajim.connections[account].set_password(passphrase) + del self.pass_dialog[account] + + def on_cancel(): + self.roster.set_state(account, 'offline') + self.roster.update_status_combobox() + del self.pass_dialog[account] + + self.pass_dialog[account] = dialogs.PassphraseDialog( + _('Password Required'), text, _('Save password'), ok_handler=on_ok, + cancel_handler=on_cancel) + + def handle_event_roster_info(self, account, array): + #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) + jid = array[0] + name = array[1] + sub = array[2] + ask = array[3] + groups = array[4] + contacts = gajim.contacts.get_contacts(account, jid) + if (not sub or sub == 'none') and (not ask or ask == 'none') and \ + not name and not groups: + # contact removed us. + if contacts: + self.roster.remove_contact(jid, account, backend=True) + return + elif not contacts: + if sub == 'remove': + return + # Add new contact to roster + contact = gajim.contacts.create_contact(jid=jid, name=name, + groups=groups, show='offline', sub=sub, ask=ask) + gajim.contacts.add_contact(account, contact) + self.roster.add_contact(jid, account) + else: + # it is an existing contact that might has changed + re_place = False + # If contact has changed (sub, ask or group) update roster + # Mind about observer status changes: + # According to xep 0162, a contact is not an observer anymore when + # we asked for auth, so also remove him if ask changed + old_groups = contacts[0].groups + if contacts[0].sub != sub or contacts[0].ask != ask\ + or old_groups != groups: + re_place = True + # c.get_shown_groups() has changed. Reflect that in roster_winodow + self.roster.remove_contact(jid, account, force=True) + for contact in contacts: + contact.name = name or '' + contact.sub = sub + contact.ask = ask + contact.groups = groups or [] + if re_place: + self.roster.add_contact(jid, account) + # Refilter and update old groups + for group in old_groups: + self.roster.draw_group(group, account) + else: + self.roster.draw_contact(jid, account) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('RosterInfo', (account, array)) + + def handle_event_bookmarks(self, account, bms): + # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) + # We received a bookmark item from the server (JEP48) + # Auto join GC windows if neccessary + + self.roster.set_actions_menu_needs_rebuild() + invisible_show = gajim.SHOW_LIST.index('invisible') + # do not autojoin if we are invisible + if gajim.connections[account].connected == invisible_show: + return + + self.auto_join_bookmarks(account) + + def handle_event_file_send_error(self, account, array): + jid = array[0] + file_props = array[1] + ft = self.instances['file_transfers'] + ft.set_status(file_props['type'], file_props['sid'], 'stop') + + if helpers.allow_popup_window(account): + ft.show_send_error(file_props) + return + + self.add_event(account, jid, 'file-send-error', file_props) + + if helpers.allow_showing_notification(account): + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('File Transfer Error') + notify.popup(event_type, jid, account, 'file-send-error', path, + event_type, file_props['name']) + + def handle_event_gmail_notify(self, account, array): + jid = array[0] + gmail_new_messages = int(array[1]) + gmail_messages_list = array[2] + if gajim.config.get('notify_on_new_gmail_email'): + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'new_email_recv.png') + title = _('New mail on %(gmail_mail_address)s') % \ + {'gmail_mail_address': jid} + text = i18n.ngettext('You have %d new mail conversation', + 'You have %d new mail conversations', gmail_new_messages, + gmail_new_messages, gmail_new_messages) + + if gajim.config.get('notify_on_new_gmail_email_extra'): + cnt = 0 + for gmessage in gmail_messages_list: + #FIXME: emulate Gtalk client popups. find out what they parse and + # how they decide what to show each message has a 'From', + # 'Subject' and 'Snippet' field + if cnt >=5: + break + senders = ',\n '.join(reversed(gmessage['From'])) + text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ + {'from_address': senders, 'subject': gmessage['Subject'], + 'snippet': gmessage['Snippet']} + cnt += 1 + + if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): + helpers.play_sound('gmail_received') + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + notify.popup(_('New E-mail'), jid, account, 'gmail', + path_to_image=path, title=title, + text=text) + + if self.remote_ctrl: + self.remote_ctrl.raise_signal('NewGmail', (account, array)) + + def handle_event_file_request_error(self, account, array): + # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) + jid, file_props, errmsg = array + ft = self.instances['file_transfers'] + ft.set_status(file_props['type'], file_props['sid'], 'stop') + errno = file_props['error'] + + if helpers.allow_popup_window(account): + if errno in (-4, -5): + ft.show_stopped(jid, file_props, errmsg) + else: + ft.show_request_error(file_props) + return + + if errno in (-4, -5): + msg_type = 'file-error' + else: + msg_type = 'file-request-error' + + self.add_event(account, jid, msg_type, file_props) + + if helpers.allow_showing_notification(account): + # check if we should be notified + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') + + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('File Transfer Error') + notify.popup(event_type, jid, account, msg_type, path, + title = event_type, text = file_props['name']) + + def handle_event_file_request(self, account, array): + jid = array[0] + if jid not in gajim.contacts.get_jid_list(account): + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_contact(jid=jid, name='', + groups=[_('Not in Roster')], show='not in roster', status='', + sub='none', keyID=keyID) + gajim.contacts.add_contact(account, contact) + self.roster.add_contact(contact.jid, account) + file_props = array[1] + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + + if helpers.allow_popup_window(account): + self.instances['file_transfers'].show_file_request(account, contact, + file_props) + return + + self.add_event(account, jid, 'file-request', file_props) + + if helpers.allow_showing_notification(account): + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', + 'ft_request.png') + txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( + account, jid) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + event_type = _('File Transfer Request') + notify.popup(event_type, jid, account, 'file-request', + path_to_image = path, title = event_type, text = txt) + + def handle_event_file_error(self, title, message): + dialogs.ErrorDialog(title, message) + + def handle_event_file_progress(self, account, file_props): + if time.time() - self.last_ftwindow_update > 0.5: + # update ft window every 500ms + self.last_ftwindow_update = time.time() + self.instances['file_transfers'].set_progress(file_props['type'], + file_props['sid'], file_props['received-len']) + + def handle_event_file_rcv_completed(self, account, file_props): + ft = self.instances['file_transfers'] + if file_props['error'] == 0: + ft.set_progress(file_props['type'], file_props['sid'], + file_props['received-len']) + else: + ft.set_status(file_props['type'], file_props['sid'], 'stop') + if 'stalled' in file_props and file_props['stalled'] or \ + 'paused' in file_props and file_props['paused']: + return + if file_props['type'] == 'r': # we receive a file + jid = unicode(file_props['sender']) + else: # we send a file + jid = unicode(file_props['receiver']) + + if helpers.allow_popup_window(account): + if file_props['error'] == 0: + if gajim.config.get('notify_on_file_complete'): + ft.show_completed(jid, file_props) + elif file_props['error'] == -1: + ft.show_stopped(jid, file_props, + error_msg=_('Remote contact stopped transfer')) + elif file_props['error'] == -6: + ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) + return + + msg_type = '' + event_type = '' + if file_props['error'] == 0 and gajim.config.get( + 'notify_on_file_complete'): + msg_type = 'file-completed' + event_type = _('File Transfer Completed') + elif file_props['error'] in (-1, -6): + msg_type = 'file-stopped' + event_type = _('File Transfer Stopped') + + if event_type == '': + # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) + # this should never happen but it does. see process_result() in socks5.py + # who calls this func (sth is really wrong unless this func is also registered + # as progress_cb + return + + if msg_type: + self.add_event(account, jid, msg_type, file_props) + + if file_props is not None: + if file_props['type'] == 'r': + # get the name of the sender, as it is in the roster + sender = unicode(file_props['sender']).split('/')[0] + name = gajim.contacts.get_first_contact_from_jid(account, + sender).get_shown_name() + filename = os.path.basename(file_props['file-name']) + if event_type == _('File Transfer Completed'): + txt = _('You successfully received %(filename)s from %(name)s.')\ + % {'filename': filename, 'name': name} + img = 'ft_done.png' + else: # ft stopped + txt = _('File transfer of %(filename)s from %(name)s stopped.')\ + % {'filename': filename, 'name': name} + img = 'ft_stopped.png' + else: + receiver = file_props['receiver'] + if hasattr(receiver, 'jid'): + receiver = receiver.jid + receiver = receiver.split('/')[0] + # get the name of the contact, as it is in the roster + name = gajim.contacts.get_first_contact_from_jid(account, + receiver).get_shown_name() + filename = os.path.basename(file_props['file-name']) + if event_type == _('File Transfer Completed'): + txt = _('You successfully sent %(filename)s to %(name)s.')\ + % {'filename': filename, 'name': name} + img = 'ft_done.png' + else: # ft stopped + txt = _('File transfer of %(filename)s to %(name)s stopped.')\ + % {'filename': filename, 'name': name} + img = 'ft_stopped.png' + img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img) + path = gtkgui_helpers.get_path_to_generic_or_avatar(img) + else: + txt = '' + + if gajim.config.get('notify_on_file_complete') and \ + (gajim.config.get('autopopupaway') or \ + gajim.connections[account].connected in (2, 3)): + # we want to be notified and we are online/chat or we don't mind + # bugged when away/na/busy + notify.popup(event_type, jid, account, msg_type, path_to_image = path, + title = event_type, text = txt) + + def handle_event_stanza_arrived(self, account, stanza): + if account not in self.instances: + return + if 'xml_console' in self.instances[account]: + self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') + + def handle_event_stanza_sent(self, account, stanza): + if account not in self.instances: + return + if 'xml_console' in self.instances[account]: + self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') + + def handle_event_vcard_published(self, account, array): + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.vcard_published() + for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ + self.minimized_controls[account].values(): + if gc_control.account == account: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + status = gajim.connections[account].status + gajim.connections[account].send_gc_status(gc_control.nick, + gc_control.room_jid, show, status) + + def handle_event_vcard_not_published(self, account, array): + if 'profile' in self.instances[account]: + win = self.instances[account]['profile'] + win.vcard_not_published() + + def ask_offline_status(self, account): + for contact in gajim.contacts.iter_contacts(account): + gajim.connections[account].request_last_status_time(contact.jid, + contact.resource) + + def handle_event_signed_in(self, account, empty): + '''SIGNED_IN event is emitted when we sign in, so handle it''' + # block signed in notifications for 30 seconds + gajim.block_signed_in_notifications[account] = True + self.roster.set_actions_menu_needs_rebuild() + self.roster.draw_account(account) + state = self.sleeper.getState() + connected = gajim.connections[account].connected + if gajim.config.get('ask_offline_status_on_connection'): + # Ask offline status in 1 minute so w'are sure we got all online + # presences + gobject.timeout_add_seconds(60, self.ask_offline_status, account) + if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): + # we go online or free for chat, so we activate auto status + gajim.sleeper_state[account] = 'online' + elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ + (state == common.sleepy.STATE_XA and connected == 5)): + # If we are autoaway/xa and come back after a disconnection, do nothing + # Else disable autoaway + gajim.sleeper_state[account] = 'off' + invisible_show = gajim.SHOW_LIST.index('invisible') + # We cannot join rooms if we are invisible + if gajim.connections[account].connected == invisible_show: + return + # join already open groupchats + for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ + + self.minimized_controls[account].values(): + if account != gc_control.account: + continue + room_jid = gc_control.room_jid + if room_jid in gajim.gc_connected[account] and \ + gajim.gc_connected[account][room_jid]: + continue + nick = gc_control.nick + password = gajim.gc_passwords.get(room_jid, '') + gajim.connections[account].join_gc(nick, room_jid, password) + + def handle_event_metacontacts(self, account, tags_list): + gajim.contacts.define_metacontacts(account, tags_list) + self.roster.redraw_metacontacts(account) + + def handle_atom_entry(self, account, data): + atom_entry, = data + AtomWindow.newAtomEntry(atom_entry) + + def handle_event_failed_decrypt(self, account, data): + jid, tim, session = data + + details = _('Unable to decrypt message from ' + '%s\nIt may have been tampered with.') % jid + + ctrl = session.control + if ctrl: + ctrl.print_conversation_line(details, 'status', '', tim) + else: + dialogs.WarningDialog(_('Unable to decrypt message'), + details) + + # terminate the session + session.terminate_e2e() + session.conn.delete_session(jid, session.thread_id) + + # restart the session + if ctrl: + ctrl.begin_e2e_negotiation() + + def handle_event_privacy_lists_received(self, account, data): + # ('PRIVACY_LISTS_RECEIVED', account, list) + if account not in self.instances: + return + if 'privacy_lists' in self.instances[account]: + self.instances[account]['privacy_lists'].privacy_lists_received(data) + + def handle_event_privacy_list_received(self, account, data): + # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) + if account not in self.instances: + return + name = data[0] + rules = data[1] + if 'privacy_list_%s' % name in self.instances[account]: + self.instances[account]['privacy_list_%s' % name].\ + privacy_list_received(rules) + if name == 'block': + gajim.connections[account].blocked_contacts = [] + gajim.connections[account].blocked_groups = [] + gajim.connections[account].blocked_list = [] + gajim.connections[account].blocked_all = False + for rule in rules: + if not 'type' in rule: + gajim.connections[account].blocked_all = True + elif rule['type'] == 'jid' and rule['action'] == 'deny': + gajim.connections[account].blocked_contacts.append(rule['value']) + elif rule['type'] == 'group' and rule['action'] == 'deny': + gajim.connections[account].blocked_groups.append(rule['value']) + gajim.connections[account].blocked_list.append(rule) + #elif rule['type'] == "group" and action == "deny": + # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] + # self.store.append([text_item]) + # self.global_rules.append(rule) + #else: + # self.global_rules_to_append.append(rule) + if 'blocked_contacts' in self.instances[account]: + self.instances[account]['blocked_contacts'].\ + privacy_list_received(rules) + + def handle_event_privacy_lists_active_default(self, account, data): + if not data: + return + # Send to all privacy_list_* windows as we can't know which one asked + for win in self.instances[account]: + if win.startswith('privacy_list_'): + self.instances[account][win].check_active_default(data) + + def handle_event_privacy_list_removed(self, account, name): + # ('PRIVACY_LISTS_REMOVED', account, name) + if account not in self.instances: + return + if 'privacy_lists' in self.instances[account]: + self.instances[account]['privacy_lists'].privacy_list_removed(name) + + def handle_event_zc_name_conflict(self, account, data): + def on_ok(new_name): + gajim.config.set_per('accounts', account, 'name', new_name) + status = gajim.connections[account].status + gajim.connections[account].username = new_name + gajim.connections[account].change_status(status, '') + def on_cancel(): + gajim.connections[account].change_status('offline','') + + dlg = dialogs.InputDialog(_('Username Conflict'), + _('Please type a new username for your local account'), input_str=data, + is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) + + def handle_event_ping_sent(self, account, contact): + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Ping?'), 'status') + + def handle_event_ping_reply(self, account, data): + contact = data[0] + seconds = data[1] + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status') + + def handle_event_ping_error(self, account, contact): + if contact.jid == contact.get_full_jid(): + # If contact is a groupchat user + jids = [contact.jid] + else: + jids = [contact.jid, contact.get_full_jid()] + for jid in jids: + ctrl = self.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.print_conversation(_('Error.'), 'status') + + def handle_event_search_form(self, account, data): + # ('SEARCH_FORM', account, (jid, dataform, is_dataform)) + if data[0] not in self.instances[account]['search']: + return + self.instances[account]['search'][data[0]].on_form_arrived(data[1], + data[2]) + + def handle_event_search_result(self, account, data): + # ('SEARCH_RESULT', account, (jid, dataform, is_dataform)) + if data[0] not in self.instances[account]['search']: + return + self.instances[account]['search'][data[0]].on_result_arrived(data[1], + data[2]) + + def handle_event_resource_conflict(self, account, data): + # ('RESOURCE_CONFLICT', account, ()) + # First we go offline, but we don't overwrite status message + self.roster.send_status(account, 'offline', + gajim.connections[account].status) + def on_ok(new_resource): + gajim.config.set_per('accounts', account, 'resource', new_resource) + self.roster.send_status(account, gajim.connections[account].old_show, + gajim.connections[account].status) + dlg = dialogs.InputDialog(_('Resource Conflict'), + _('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource, + is_modal = False, ok_handler = on_ok) + + def handle_event_pep_config(self, account, data): + # ('PEP_CONFIG', account, (node, form)) + if 'pep_services' in self.instances[account]: + self.instances[account]['pep_services'].config(data[0], data[1]) + + def handle_event_roster_item_exchange(self, account, data): + # data = (action in [add, delete, modify], exchange_list, jid_from) + dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2]) + + def handle_event_unique_room_id_supported(self, account, data): + '''Receive confirmation that unique_room_id are supported''' + # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) + instance = data[1] + instance.unique_room_id_supported(data[0], data[2]) + + def handle_event_unique_room_id_unsupported(self, account, data): + # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) + instance = data[1] + instance.unique_room_id_error(data[0]) + + def handle_event_ssl_error(self, account, data): + # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) + server = gajim.config.get_per('accounts', account, 'hostname') + + def on_ok(is_checked): + del self.instances[account]['online_dialog']['ssl_error'] + if is_checked[0]: + # Check if cert is already in file + certs = '' + if os.path.isfile(gajim.MY_CACERTS): + f = open(gajim.MY_CACERTS) + certs = f.read() + f.close() + if data[2] in certs: + dialogs.ErrorDialog(_('Certificate Already in File'), + _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) + else: + f = open(gajim.MY_CACERTS, 'a') + f.write(server + '\n') + f.write(data[2] + '\n\n') + f.close() + gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', + data[3]) + if is_checked[1]: + ignore_ssl_errors = gajim.config.get_per('accounts', account, + 'ignore_ssl_errors').split() + ignore_ssl_errors.append(str(data[1])) + gajim.config.set_per('accounts', account, 'ignore_ssl_errors', + ' '.join(ignore_ssl_errors)) + gajim.connections[account].ssl_certificate_accepted() + + def on_cancel(): + del self.instances[account]['online_dialog']['ssl_error'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + + pritext = _('Error verifying SSL certificate') + sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]} + if data[1] in (18, 27): + checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] + else: + checktext1 = '' + checktext2 = _('Ignore this error for this certificate.') + if 'ssl_error' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['ssl_error'].destroy() + self.instances[account]['online_dialog']['ssl_error'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, + checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) + + def handle_event_fingerprint_error(self, account, data): + # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) + def on_yes(is_checked): + del self.instances[account]['online_dialog']['fingerprint_error'] + gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', + data[0]) + # Reset the ignored ssl errors + gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') + gajim.connections[account].ssl_certificate_accepted() + def on_no(): + del self.instances[account]['online_dialog']['fingerprint_error'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('SSL certificate error') + sectext = _('It seems the SSL certificate of account %(account)s has ' + 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' + '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' + ' the fingerprint of the certificate?') % {'account': account, + 'old': gajim.config.get_per('accounts', account, + 'ssl_fingerprint_sha1'), 'new': data[0]} + if 'fingerprint_error' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['fingerprint_error'].destroy() + self.instances[account]['online_dialog']['fingerprint_error'] = \ + dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, + on_response_no=on_no) + + def handle_event_plain_connection(self, account, data): + # ('PLAIN_CONNECTION', account, (connection)) + server = gajim.config.get_per('accounts', account, 'hostname') + def on_ok(is_checked): + if not is_checked[0]: + on_cancel() + return + # On cancel call del self.instances, so don't call it another time + # before + del self.instances[account]['online_dialog']['plain_connection'] + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_plaintext_connection', False) + gajim.connections[account].connection_accepted(data[0], 'plain') + def on_cancel(): + del self.instances[account]['online_dialog']['plain_connection'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password on an unencrypted ' + 'connection. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('Do _not ask me again') + if 'plain_connection' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['plain_connection'].destroy() + self.instances[account]['online_dialog']['plain_connection'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, + checktext1, checktext2, on_response_ok=on_ok, + on_response_cancel=on_cancel, is_modal=False) + + def handle_event_insecure_ssl_connection(self, account, data): + # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) + server = gajim.config.get_per('accounts', account, 'hostname') + def on_ok(is_checked): + del self.instances[account]['online_dialog']['insecure_ssl'] + if not is_checked[0]: + on_cancel() + return + if is_checked[1]: + gajim.config.set_per('accounts', account, + 'warn_when_insecure_ssl_connection', False) + if gajim.connections[account].connected == 0: + # We have been disconnecting (too long time since window is opened) + # re-connect with auto-accept + gajim.connections[account].connection_auto_accepted = True + show, msg = gajim.connections[account].continue_connect_info[:2] + self.roster.send_status(account, show, msg) + return + gajim.connections[account].connection_accepted(data[0], data[1]) + def on_cancel(): + del self.instances[account]['online_dialog']['insecure_ssl'] + gajim.connections[account].disconnect(on_purpose=True) + self.handle_event_status(account, 'offline') + pritext = _('Insecure connection') + sectext = _('You are about to send your password on an insecure ' + 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') + checktext1 = _('Yes, I really want to connect insecurely') + checktext2 = _('Do _not ask me again') + if 'insecure_ssl' in self.instances[account]['online_dialog']: + self.instances[account]['online_dialog']['insecure_ssl'].destroy() + self.instances[account]['online_dialog']['insecure_ssl'] = \ + dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, + checktext1, checktext2, on_response_ok=on_ok, + on_response_cancel=on_cancel, is_modal=False) + + def handle_event_pubsub_node_removed(self, account, data): + # ('PUBSUB_NODE_REMOVED', account, (jid, node)) + if 'pep_services' in self.instances[account]: + if data[0] == gajim.get_jid_from_account(account): + self.instances[account]['pep_services'].node_removed(data[1]) + + def handle_event_pubsub_node_not_removed(self, account, data): + # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) + if data[0] == gajim.get_jid_from_account(account): + dialogs.WarningDialog(_('PEP node was not removed'), + _('PEP node %(node)s was not removed: %(message)s') % { + 'node': data[1], 'message': data[2]}) + + def register_handlers(self): + self.handlers = { + 'ROSTER': self.handle_event_roster, + 'WARNING': self.handle_event_warning, + 'ERROR': self.handle_event_error, + 'INFORMATION': self.handle_event_information, + 'ERROR_ANSWER': self.handle_event_error_answer, + 'STATUS': self.handle_event_status, + 'NOTIFY': self.handle_event_notify, + 'MSGERROR': self.handle_event_msgerror, + 'MSGSENT': self.handle_event_msgsent, + 'MSGNOTSENT': self.handle_event_msgnotsent, + 'SUBSCRIBED': self.handle_event_subscribed, + 'UNSUBSCRIBED': self.handle_event_unsubscribed, + 'SUBSCRIBE': self.handle_event_subscribe, + 'AGENT_ERROR_INFO': self.handle_event_agent_info_error, + 'AGENT_ERROR_ITEMS': self.handle_event_agent_items_error, + 'AGENT_REMOVED': self.handle_event_agent_removed, + 'REGISTER_AGENT_INFO': self.handle_event_register_agent_info, + 'AGENT_INFO_ITEMS': self.handle_event_agent_info_items, + 'AGENT_INFO_INFO': self.handle_event_agent_info_info, + 'QUIT': self.handle_event_quit, + 'NEW_ACC_CONNECTED': self.handle_event_new_acc_connected, + 'NEW_ACC_NOT_CONNECTED': self.handle_event_new_acc_not_connected, + 'ACC_OK': self.handle_event_acc_ok, + 'ACC_NOT_OK': self.handle_event_acc_not_ok, + 'MYVCARD': self.handle_event_myvcard, + 'VCARD': self.handle_event_vcard, + 'LAST_STATUS_TIME': self.handle_event_last_status_time, + 'OS_INFO': self.handle_event_os_info, + 'ENTITY_TIME': self.handle_event_entity_time, + 'GC_NOTIFY': self.handle_event_gc_notify, + 'GC_MSG': self.handle_event_gc_msg, + 'GC_SUBJECT': self.handle_event_gc_subject, + 'GC_CONFIG': self.handle_event_gc_config, + 'GC_CONFIG_CHANGE': self.handle_event_gc_config_change, + 'GC_INVITATION': self.handle_event_gc_invitation, + 'GC_AFFILIATION': self.handle_event_gc_affiliation, + 'GC_PASSWORD_REQUIRED': self.handle_event_gc_password_required, + 'BAD_PASSPHRASE': self.handle_event_bad_passphrase, + 'ROSTER_INFO': self.handle_event_roster_info, + 'BOOKMARKS': self.handle_event_bookmarks, + 'CON_TYPE': self.handle_event_con_type, + 'CONNECTION_LOST': self.handle_event_connection_lost, + 'FILE_REQUEST': self.handle_event_file_request, + 'GMAIL_NOTIFY': self.handle_event_gmail_notify, + 'FILE_REQUEST_ERROR': self.handle_event_file_request_error, + 'FILE_SEND_ERROR': self.handle_event_file_send_error, + 'STANZA_ARRIVED': self.handle_event_stanza_arrived, + 'STANZA_SENT': self.handle_event_stanza_sent, + 'HTTP_AUTH': self.handle_event_http_auth, + 'VCARD_PUBLISHED': self.handle_event_vcard_published, + 'VCARD_NOT_PUBLISHED': self.handle_event_vcard_not_published, + 'ASK_NEW_NICK': self.handle_event_ask_new_nick, + 'SIGNED_IN': self.handle_event_signed_in, + 'METACONTACTS': self.handle_event_metacontacts, + 'ATOM_ENTRY': self.handle_atom_entry, + 'FAILED_DECRYPT': self.handle_event_failed_decrypt, + 'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received, + 'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received, + 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ + self.handle_event_privacy_lists_active_default, + 'PRIVACY_LIST_REMOVED': self.handle_event_privacy_list_removed, + 'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict, + 'PING_SENT': self.handle_event_ping_sent, + 'PING_REPLY': self.handle_event_ping_reply, + 'PING_ERROR': self.handle_event_ping_error, + 'SEARCH_FORM': self.handle_event_search_form, + 'SEARCH_RESULT': self.handle_event_search_result, + 'RESOURCE_CONFLICT': self.handle_event_resource_conflict, + 'ROSTERX': self.handle_event_roster_item_exchange, + 'PEP_CONFIG': self.handle_event_pep_config, + 'UNIQUE_ROOM_ID_UNSUPPORTED': \ + self.handle_event_unique_room_id_unsupported, + 'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported, + 'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required, + 'GPG_ALWAYS_TRUST': self.handle_event_gpg_always_trust, + 'PASSWORD_REQUIRED': self.handle_event_password_required, + 'SSL_ERROR': self.handle_event_ssl_error, + 'FINGERPRINT_ERROR': self.handle_event_fingerprint_error, + 'PLAIN_CONNECTION': self.handle_event_plain_connection, + 'INSECURE_SSL_CONNECTION': self.handle_event_insecure_ssl_connection, + 'PUBSUB_NODE_REMOVED': self.handle_event_pubsub_node_removed, + 'PUBSUB_NODE_NOT_REMOVED': self.handle_event_pubsub_node_not_removed, + } + + def dispatch(self, event, account, data): + ''' + Dispatches an network event to the event handlers of this class + ''' + if event not in self.handlers: + log.warning('Unknown event %s dispatched to GUI: %s' % (event, data)) + else: + log.debug('Event %s distpached to GUI: %s' % (event, data)) + self.handlers[event](account, data) + + +################################################################################ +### Methods dealing with gajim.events +################################################################################ + + def add_event(self, account, jid, type_, event_args): + '''add an event to the gajim.events var''' + # We add it to the gajim.events queue + # Do we have a queue? + jid = gajim.get_jid_without_resource(jid) + no_queue = len(gajim.events.get_events(account, jid)) == 0 + # type_ can be gc-invitation file-send-error file-error file-request-error + # file-request file-completed file-stopped + # event_type can be in advancedNotificationWindow.events_list + event_types = {'file-request': 'ft_request', + 'file-completed': 'ft_finished'} + event_type = event_types.get(type_) + show_in_roster = notify.get_show_in_roster(event_type, account, jid) + show_in_systray = notify.get_show_in_systray(event_type, account, jid) + event = gajim.events.create_event(type_, event_args, + show_in_roster=show_in_roster, + show_in_systray=show_in_systray) + gajim.events.add_event(account, jid, event) + + self.roster.show_title() + if no_queue: # We didn't have a queue: we change icons + if not gajim.contacts.get_contact_with_highest_priority(account, jid): + if type_ == 'gc-invitation': + self.roster.add_groupchat(jid, account, status='offline') + else: + # add contact to roster ("Not In The Roster") if he is not + self.roster.add_to_not_in_the_roster(account, jid) + else: + self.roster.draw_contact(jid, account) + + # Select the big brother contact in roster, it's visible because it has + # events. + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + nearby_family, bb_jid, bb_account = \ + self.roster._get_nearby_family_and_big_brother(family, account) + else: + bb_jid, bb_account = jid, account + self.roster.select_contact(bb_jid, bb_account) + + def handle_event(self, account, fjid, type_): + w = None + ctrl = None + session = None + + resource = gajim.get_resource_from_jid(fjid) + jid = gajim.get_jid_without_resource(fjid) + + if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): + w = self.msg_win_mgr.get_window(jid, account) + if jid in self.minimized_controls[account]: + self.roster.on_groupchat_maximized(None, jid, account) + return + else: + ctrl = self.msg_win_mgr.get_gc_control(jid, account) + + elif type_ in ('printed_chat', 'chat', ''): + # '' is for log in/out notifications + + if type_ != '': + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + event = gajim.events.get_first_event(account, jid, type_) + if not event: + return + + if type_ == 'printed_chat': + ctrl = event.parameters[0] + elif type_ == 'chat': + session = event.parameters[8] + ctrl = session.control + elif type_ == '': + ctrl = self.msg_win_mgr.get_control(fjid, account) + + if not ctrl: + highest_contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + # jid can have a window if this resource was lower when he sent + # message and is now higher because the other one is offline + if resource and highest_contact.resource == resource and \ + not self.msg_win_mgr.has_window(jid, account): + # remove resource of events too + gajim.events.change_jid(account, fjid, jid) + resource = None + fjid = jid + contact = None + if resource: + contact = gajim.contacts.get_contact(account, jid, resource) + if not contact: + contact = highest_contact + + ctrl = self.new_chat(contact, account, resource = resource, session = session) + + gajim.last_message_time[account][jid] = 0 # long time ago + + w = ctrl.parent_win + elif type_ in ('printed_pm', 'pm'): + # assume that the most recently updated control we have for this party + # is the one that this event was in + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + event = gajim.events.get_first_event(account, jid, type_) + + if type_ == 'printed_pm': + ctrl = event.parameters[0] + elif type_ == 'pm': + session = event.parameters[8] + + if session and session.control: + ctrl = session.control + elif not ctrl: + room_jid = jid + nick = resource + gc_contact = gajim.contacts.get_gc_contact(account, room_jid, + nick) + if gc_contact: + show = gc_contact.show + else: + show = 'offline' + gc_contact = gajim.contacts.create_gc_contact( + room_jid = room_jid, name = nick, show = show) + + if not session: + session = gajim.connections[account].make_new_session( + fjid, None, type_='pm') + + self.new_private_chat(gc_contact, account, session=session) + ctrl = session.control + + w = ctrl.parent_win + elif type_ in ('normal', 'file-request', 'file-request-error', + 'file-send-error', 'file-error', 'file-stopped', 'file-completed'): + # Get the first single message event + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + # default to jid without resource + event = gajim.events.get_first_event(account, jid, type_) + if not event: + return + # Open the window + self.roster.open_event(account, jid, event) + else: + # Open the window + self.roster.open_event(account, fjid, event) + elif type_ == 'gmail': + url=gajim.connections[account].gmail_url + if url: + helpers.launch_browser_mailer('url', url) + elif type_ == 'gc-invitation': + event = gajim.events.get_first_event(account, jid, type_) + data = event.parameters + dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], + data[1], data[3]) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'subscription_request': + event = gajim.events.get_first_event(account, jid, type_) + data = event.parameters + dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + elif type_ == 'unsubscribed': + event = gajim.events.get_first_event(account, jid, type_) + contact = event.parameters + self.show_unsubscribed_dialog(account, contact) + gajim.events.remove_events(account, jid, event) + self.roster.draw_contact(jid, account) + if w: + w.set_active_tab(ctrl) + w.window.window.focus(gtk.get_current_event_time()) + # Using isinstance here because we want to catch all derived types + if isinstance(ctrl, ChatControlBase): + tv = ctrl.conv_textview + tv.scroll_to_end() + +################################################################################ +### Methods dealing with emoticons +################################################################################ + + def image_is_ok(self, image): + if not os.path.exists(image): + return False + img = gtk.Image() + try: + img.set_from_file(image) + except Exception: + return False + t = img.get_storage_type() + if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: + return False + return True + + @property + def basic_pattern_re(self): + try: + return self._basic_pattern_re + except AttributeError: + self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) + return self._basic_pattern_re + + @property + def emot_and_basic_re(self): + try: + return self._emot_and_basic_re + except AttributeError: + self._emot_and_basic_re = re.compile(self.emot_and_basic, + re.IGNORECASE + re.UNICODE) + return self._emot_and_basic_re + + @property + def sth_at_sth_dot_sth_re(self): + try: + return self._sth_at_sth_dot_sth_re + except AttributeError: + self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) + return self._sth_at_sth_dot_sth_re + + @property + def invalid_XML_chars_re(self): + try: + return self._invalid_XML_chars_re + except AttributeError: + self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) + return self._invalid_XML_chars_re + + def make_regexps(self): + # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) + # one escapes the metachars with \ + # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' + # \s matches any whitespace character + # \w any alphanumeric character + # \W any non-alphanumeric character + # \b means word boundary. This is a zero-width assertion that + # matches only at the beginning or end of a word. + # ^ matches at the beginning of lines + # + # * means 0 or more times + # + means 1 or more times + # ? means 0 or 1 time + # | means or + # [^*] anything but '*' (inside [] you don't have to escape metachars) + # [^\s*] anything but whitespaces and '*' + # (? in the matching string don't match ? or ) etc.. if at the end + # so http://be) will match http://be and http://be)be) will match http://be)be + + legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ + r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + # NOTE: it's ok to catch www.gr such stuff exist! + + #FIXME: recognize xmpp: and treat it specially + links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ + r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ + r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" + + #2nd one: at_least_one_char@at_least_one_char.at_least_one_char + mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' + + #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) + #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* + formatting = r'|(?> sys.stderr, err_str + # it is good to notify the user + # in case he or she cannot see the output of the console + dialogs.ErrorDialog(_('Could not save your settings and preferences'), + err_str) + sys.exit() + + def save_avatar_files(self, jid, photo, puny_nick = None, local = False): + '''Saves an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image.''' + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + if puny_nick: + path_to_file = os.path.join(path_to_file, puny_nick) + # remove old avatars + for typ in ('jpeg', 'png'): + if local: + path_to_original_file = path_to_file + '_local'+ '.' + typ + else: + path_to_original_file = path_to_file + '.' + typ + if os.path.isfile(path_to_original_file): + os.remove(path_to_original_file) + if local and photo: + pixbuf = photo + typ = 'png' + extension = '_local.png' # save local avatars as png file + else: + pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) + if pixbuf is None: + return + extension = '.' + typ + if typ not in ('jpeg', 'png'): + gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) + typ = 'png' + extension = '.png' + path_to_original_file = path_to_file + extension + try: + pixbuf.save(path_to_original_file, typ) + except Exception, e: + log.error('Error writing avatar file %s: %s' % (path_to_original_file, + str(e))) + # Generate and save the resized, color avatar + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') + if pixbuf: + path_to_normal_file = path_to_file + '_notif_size_colored' + extension + try: + pixbuf.save(path_to_normal_file, 'png') + except Exception, e: + log.error('Error writing avatar file %s: %s' % \ + (path_to_original_file, str(e))) + # Generate and save the resized, black and white avatar + bwbuf = gtkgui_helpers.get_scaled_pixbuf( + gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') + if bwbuf: + path_to_bw_file = path_to_file + '_notif_size_bw' + extension + try: + bwbuf.save(path_to_bw_file, 'png') + except Exception, e: + log.error('Error writing avatar file %s: %s' % \ + (path_to_original_file, str(e))) + + def remove_avatar_files(self, jid, puny_nick = None, local = False): + '''remove avatar files of a jid''' + puny_jid = helpers.sanitize_filename(jid) + path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + if puny_nick: + path_to_file = os.path.join(path_to_file, puny_nick) + for ext in ('.jpeg', '.png'): + if local: + ext = '_local' + ext + path_to_original_file = path_to_file + ext + if os.path.isfile(path_to_file + ext): + os.remove(path_to_file + ext) + if os.path.isfile(path_to_file + '_notif_size_colored' + ext): + os.remove(path_to_file + '_notif_size_colored' + ext) + if os.path.isfile(path_to_file + '_notif_size_bw' + ext): + os.remove(path_to_file + '_notif_size_bw' + ext) + + def auto_join_bookmarks(self, account): + '''autojoin bookmarked GCs that have 'auto join' on for this account''' + for bm in gajim.connections[account].bookmarks: + if bm['autojoin'] in ('1', 'true'): + jid = bm['jid'] + # Only join non-opened groupchats. Opened one are already + # auto-joined on re-connection + if not jid in gajim.gc_connected[account]: + # we are not already connected + minimize = bm['minimize'] in ('1', 'true') + gajim.interface.join_gc_room(account, jid, bm['nick'], + bm['password'], minimize = minimize) + elif jid in self.minimized_controls[account]: + # more or less a hack: + # On disconnect the minimized gc contact instances + # were set to offline. Reconnect them to show up in the roster. + self.roster.add_groupchat(jid, account) + + def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, + nick): + '''add a bookmark for this account, sorted in bookmark list''' + bm = { + 'name': name, + 'jid': jid, + 'autojoin': autojoin, + 'minimize': minimize, + 'password': password, + 'nick': nick + } + place_found = False + index = 0 + # check for duplicate entry and respect alpha order + for bookmark in gajim.connections[account].bookmarks: + if bookmark['jid'] == bm['jid']: + dialogs.ErrorDialog( + _('Bookmark already set'), + _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) + return + if bookmark['name'] > bm['name']: + place_found = True + break + index += 1 + if place_found: + gajim.connections[account].bookmarks.insert(index, bm) + else: + gajim.connections[account].bookmarks.append(bm) + gajim.connections[account].store_bookmarks() + self.roster.set_actions_menu_needs_rebuild() + dialogs.InformationDialog( + _('Bookmark has been added successfully'), + _('You can manage your bookmarks via Actions menu in your roster.')) + + + # does JID exist only within a groupchat? + def is_pm_contact(self, fjid, account): + bare_jid = gajim.get_jid_without_resource(fjid) + + gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) + + if not gc_ctrl and \ + bare_jid in self.minimized_controls[account]: + gc_ctrl = self.minimized_controls[account][bare_jid] + + return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC + + def create_ipython_window(self): + try: + from ipython_view import IPythonView + except ImportError: + print 'ipython_view not found' + return + import pango + + if os.name == 'nt': + font = 'Lucida Console 9' + else: + font = 'Luxi Mono 10' + + window = gtk.Window() + window.set_size_request(750,550) + window.set_resizable(True) + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) + view = IPythonView() + view.modify_font(pango.FontDescription(font)) + view.set_wrap_mode(gtk.WRAP_CHAR) + sw.add(view) + window.add(sw) + window.show_all() + def on_delete(win, event): + win.hide() + return True + window.connect('delete_event',on_delete) + view.updateNamespace({'gajim': gajim}) + gajim.ipython_window = window + + def run(self): + if self.systray_capabilities and gajim.config.get('trayicon') != 'never': + self.show_systray() + + self.roster = roster_window.RosterWindow() + for account in gajim.connections: + gajim.connections[account].load_roster_from_db() + + # get instances for windows/dialogs that will show_all()/hide() + self.instances['file_transfers'] = dialogs.FileTransfersWindow() + + gobject.timeout_add(100, self.autoconnect) + timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT + if in_seconds: + gobject.timeout_add_seconds(timeout, self.process_connections) + else: + gobject.timeout_add(timeout, self.process_connections) + gobject.timeout_add_seconds(gajim.config.get( + 'check_idle_every_foo_seconds'), self.read_sleepy) + + # when using libasyncns we need to process resolver in regular intervals + if resolver.USE_LIBASYNCNS: + gobject.timeout_add(200, gajim.resolver.process) + + # setup the indicator + if gajim.HAVE_INDICATOR: + notify.setup_indicator_server() + + def remote_init(): + if gajim.config.get('remote_control'): + try: + import remote_control + self.remote_ctrl = remote_control.Remote() + except Exception: + pass + gobject.timeout_add_seconds(5, remote_init) + + for account in gajim.connections: + if gajim.config.get_per('accounts', account, 'publish_tune') and \ + dbus_support.supported: + self.enable_music_listener() + break + + def __init__(self): + gajim.interface = self + gajim.thread_interface = ThreadInterface + # This is the manager and factory of message windows set by the module + self.msg_win_mgr = None + self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, + 'closed': {}} + self.emoticons_menu = None + # handler when an emoticon is clicked in emoticons_menu + self.emoticon_menuitem_clicked = None + self.minimized_controls = {} + self.status_sent_to_users = {} + self.status_sent_to_groups = {} + self.gpg_passphrase = {} + self.pass_dialog = {} + self.default_colors = { + 'inmsgcolor': gajim.config.get('inmsgcolor'), + 'outmsgcolor': gajim.config.get('outmsgcolor'), + 'statusmsgcolor': gajim.config.get('statusmsgcolor'), + 'urlmsgcolor': gajim.config.get('urlmsgcolor'), + } + + cfg_was_read = parser.read() + gajim.logger.reset_shown_unread_messages() + # override logging settings from config (don't take care of '-q' option) + if gajim.config.get('verbose'): + logging_helpers.set_verbose() + + # Is Gajim default app? + if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): + gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() + + for account in gajim.config.get_per('accounts'): + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + gajim.ZEROCONF_ACC_NAME = account + break + # Is gnome configured to activate row on single click ? + try: + import gconf + client = gconf.client_get_default() + click_policy = client.get_string( + '/apps/nautilus/preferences/click_policy') + if click_policy == 'single': + gajim.single_click = True + except Exception: + pass + # add default status messages if there is not in the config file + if len(gajim.config.get_per('statusmsg')) == 0: + default = gajim.config.statusmsg_default + for msg in default: + gajim.config.add_per('statusmsg', msg) + gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) + gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) + gajim.config.set_per('statusmsg', msg, 'subactivity', + default[msg][2]) + gajim.config.set_per('statusmsg', msg, 'activity_text', + default[msg][3]) + gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) + gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) + #add default themes if there is not in the config file + theme = gajim.config.get('roster_theme') + if not theme in gajim.config.get_per('themes'): + gajim.config.set('roster_theme', _('default')) + if len(gajim.config.get_per('themes')) == 0: + d = ['accounttextcolor', 'accountbgcolor', 'accountfont', + 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', + 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', + 'contactfont', 'contactfontattrs', 'bannertextcolor', + 'bannerbgcolor'] + + default = gajim.config.themes_default + for theme_name in default: + gajim.config.add_per('themes', theme_name) + theme = default[theme_name] + for o in d: + gajim.config.set_per('themes', theme_name, o, + theme[d.index(o)]) + + if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: + gtkgui_helpers.autodetect_browser_mailer() + + gajim.idlequeue = idlequeue.get_idlequeue() + # resolve and keep current record of resolved hosts + gajim.resolver = resolver.get_resolver(gajim.idlequeue) + gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, + self.handle_event_file_rcv_completed, + self.handle_event_file_progress, + self.handle_event_file_error) + gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) + gajim.default_session_type = ChatControlSession + self.register_handlers() + if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ + and gajim.HAVE_ZEROCONF: + gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ + connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) + for account in gajim.config.get_per('accounts'): + if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ + gajim.config.get_per('accounts', account, 'active'): + gajim.connections[account] = common.connection.Connection(account) + + # gtk hooks + gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') + gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') + gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') + + self.instances = {} + + for a in gajim.connections: + self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, + 'search': {}, 'online_dialog': {}} + # online_dialog contains all dialogs that have a meaning only when we + # are not disconnected + self.minimized_controls[a] = {} + gajim.contacts.add_account(a) + gajim.groups[a] = {} + gajim.gc_connected[a] = {} + gajim.automatic_rooms[a] = {} + gajim.newly_added[a] = [] + gajim.to_be_removed[a] = [] + gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') + gajim.block_signed_in_notifications[a] = True + gajim.sleeper_state[a] = 0 + gajim.encrypted_chats[a] = [] + gajim.last_message_time[a] = {} + gajim.status_before_autoaway[a] = '' + gajim.transport_avatar[a] = {} + gajim.gajim_optional_features[a] = [] + gajim.caps_hash[a] = '' + + helpers.update_optional_features() + + self.remote_ctrl = None + + if gajim.config.get('networkmanager_support') and dbus_support.supported: + import network_manager_listener + + # Handle gnome screensaver + if dbus_support.supported: + def gnome_screensaver_ActiveChanged_cb(active): + if not active: + for account in gajim.connections: + if gajim.sleeper_state[account] == 'autoaway-forced': + # We came back online ofter gnome-screensaver autoaway + self.roster.send_status(account, 'online', + gajim.status_before_autoaway[account]) + gajim.status_before_autoaway[account] = '' + gajim.sleeper_state[account] = 'online' + return + if not gajim.config.get('autoaway'): + # Don't go auto away if user disabled the option + return + for account in gajim.connections: + if account not in gajim.sleeper_state or \ + not gajim.sleeper_state[account]: + continue + if gajim.sleeper_state[account] == 'online': + # we save out online status + gajim.status_before_autoaway[account] = \ + gajim.connections[account].status + # we go away (no auto status) [we pass True to auto param] + auto_message = gajim.config.get('autoaway_message') + if not auto_message: + auto_message = gajim.connections[account].status + else: + auto_message = auto_message.replace('$S','%(status)s') + auto_message = auto_message.replace('$T','%(time)s') + auto_message = auto_message % { + 'status': gajim.status_before_autoaway[account], + 'time': gajim.config.get('autoxatime') + } + self.roster.send_status(account, 'away', auto_message, + auto=True) + gajim.sleeper_state[account] = 'autoaway-forced' + + try: + bus = dbus.SessionBus() + bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, + 'ActiveChanged', 'org.gnome.ScreenSaver') + except Exception: + pass + + self.show_vcard_when_connect = [] + + self.sleeper = common.sleepy.Sleepy( + gajim.config.get('autoawaytime') * 60, # make minutes to seconds + gajim.config.get('autoxatime') * 60) + + gtkgui_helpers.make_jabber_state_images() + + self.systray_enabled = False + self.systray_capabilities = False + + if (os.name == 'nt'): + import statusicon + self.systray = statusicon.StatusIcon() + self.systray_capabilities = True + else: # use ours, not GTK+ one + # [FIXME: remove this when we migrate to 2.10 and we can do + # cool tooltips somehow and (not dying to keep) animation] + import systray + self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES + if self.systray_capabilities: + self.systray = systray.Systray() + + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') + pix = gtk.gdk.pixbuf_new_from_file(path_to_file) + # set the icon to all windows + gtk.window_set_default_icon(pix) + + self.init_emoticons() + self.make_regexps() + + # get transports type from DB + gajim.transport_type = gajim.logger.get_transports_type() + + # test is dictionnary is present for speller + if gajim.config.get('use_speller'): + lang = gajim.config.get('speller_language') + if not lang: + lang = gajim.LANG + tv = gtk.TextView() + try: + import gtkspell + spell = gtkspell.Spell(tv, lang) + except (ImportError, TypeError, RuntimeError, OSError): + dialogs.AspellDictError(lang) + + if gajim.config.get('soundplayer') == '': + # only on first time Gajim starts + commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') + for command in commands: + if helpers.is_in_path(command): + if command == 'aplay': + command += ' -q' + gajim.config.set('soundplayer', command) + break + + self.last_ftwindow_update = 0 + + self.music_track_changed_signal = None if __name__ == '__main__': def sigint_cb(num, stack): diff --git a/src/interface.py b/src/interface.py deleted file mode 100644 index 16fc9bb21..000000000 --- a/src/interface.py +++ /dev/null @@ -1,3355 +0,0 @@ -# -*- coding:utf-8 -*- -## src/gajim.py -## -## Copyright (C) 2003-2008 Yann Leboulanger -## Copyright (C) 2004-2005 Vincent Hanquez -## Copyright (C) 2005 Alex Podaras -## Norman Rasmussen -## Stéphan Kochen -## Copyright (C) 2005-2006 Dimitur Kirov -## Alex Mauer -## Copyright (C) 2005-2007 Travis Shirk -## Nikos Kouremenos -## Copyright (C) 2006 Junglecow J -## Stefan Bethge -## Copyright (C) 2006-2008 Jean-Marie Traissard -## Copyright (C) 2007 Lukas Petrovicky -## James Newton -## Copyright (C) 2007-2008 Brendan Taylor -## Julien Pivotto -## Stephan Erb -## Copyright (C) 2008 Jonathan Schleifer -## -## This file is part of Gajim. -## -## Gajim 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; version 3 only. -## -## Gajim 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 Gajim. If not, see . -## - -import os -import sys -import re -import time -import math - -import gtk -import gobject - -from common import i18n -from common import gajim - -from common import dbus_support -if dbus_support.supported: - from music_track_listener import MusicTrackListener - import dbus - -import gtkgui_helpers - - -import dialogs -import notify -import message_control - -from chat_control import ChatControlBase -from chat_control import ChatControl -from groupchat_control import GroupchatControl -from groupchat_control import PrivateChatControl - -from atom_window import AtomWindow -from session import ChatControlSession - -import common.sleepy - -from common.xmpp import idlequeue -from common.zeroconf import connection_zeroconf -from common import resolver -from common import proxy65_manager -from common import socks5 -from common import helpers -from common import dataforms -from common import passwords -from common import pep -from common import logging_helpers - -import roster_window -import profile_window -import config -from threading import Thread - -gajimpaths = common.configpaths.gajimpaths -config_filename = gajimpaths['CONFIG_FILE'] - -from common import optparser -parser = optparser.OptionsParser(config_filename) - - -import logging -log = logging.getLogger('gajim.interface') - -class Interface: - -################################################################################ -### Methods handling events from connection -################################################################################ - - def handle_event_roster(self, account, data): - #('ROSTER', account, array) - # FIXME: Those methods depend to highly on each other - # and the order in which they are called - self.roster.fill_contacts_and_groups_dicts(data, account) - self.roster.add_account_contacts(account) - self.roster.fire_up_unread_messages_events(account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Roster', (account, data)) - - def handle_event_warning(self, unused, data): - #('WARNING', account, (title_text, section_text)) - dialogs.WarningDialog(data[0], data[1]) - - def handle_event_error(self, unused, data): - #('ERROR', account, (title_text, section_text)) - dialogs.ErrorDialog(data[0], data[1]) - - def handle_event_information(self, unused, data): - #('INFORMATION', account, (title_text, section_text)) - dialogs.InformationDialog(data[0], data[1]) - - def handle_event_ask_new_nick(self, account, data): - #('ASK_NEW_NICK', account, (room_jid,)) - room_jid = data[0] - title = _('Unable to join group chat') - prompt = _('Your desired nickname in group chat %s is in use or ' - 'registered by another occupant.\nPlease specify another nickname ' - 'below:') % room_jid - check_text = _('Always use this nickname when there is a conflict') - if 'change_nick_dialog' in self.instances: - self.instances['change_nick_dialog'].add_room(account, room_jid, - prompt) - else: - self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( - account, room_jid, title, prompt) - - def handle_event_http_auth(self, account, data): - #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) - def response(account, iq_obj, answer): - self.dialog.destroy() - gajim.connections[account].build_http_auth_answer(iq_obj, answer) - - def on_yes(is_checked, account, iq_obj): - response(account, iq_obj, 'yes') - - sec_msg = _('Do you accept this request?') - if gajim.get_number_of_connected_accounts() > 1: - sec_msg = _('Do you accept this request on account %s?') % account - if data[4]: - sec_msg = data[4] + '\n' + sec_msg - self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' - '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1], - 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]), - on_response_no=(response, account, data[3], 'no')) - - def handle_event_error_answer(self, account, array): - #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) - id_, jid_from, errmsg, errcode = array - if unicode(errcode) in ('400', '403', '406') and id_: - # show the error dialog - ft = self.instances['file_transfers'] - sid = id_ - if len(id_) > 3 and id_[2] == '_': - sid = id_[3:] - if sid in ft.files_props['s']: - file_props = ft.files_props['s'][sid] - if unicode(errcode) == '400': - file_props['error'] = -3 - else: - file_props['error'] = -4 - self.handle_event_file_request_error(account, - (jid_from, file_props, errmsg)) - conn = gajim.connections[account] - conn.disconnect_transfer(file_props) - return - elif unicode(errcode) == '404': - conn = gajim.connections[account] - sid = id_ - if len(id_) > 3 and id_[2] == '_': - sid = id_[3:] - if sid in conn.files_props: - file_props = conn.files_props[sid] - self.handle_event_file_send_error(account, - (jid_from, file_props)) - conn.disconnect_transfer(file_props) - return - - ctrl = self.msg_win_mgr.get_control(jid_from, account) - if ctrl and ctrl.type_id == message_control.TYPE_GC: - ctrl.print_conversation('Error %s: %s' % (array[2], array[1])) - - def handle_event_con_type(self, account, con_type): - # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain' - gajim.con_types[account] = con_type - self.roster.draw_account(account) - - def handle_event_connection_lost(self, account, array): - # ('CONNECTION_LOST', account, [title, text]) - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'connection_lost.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - notify.popup(_('Connection Failed'), account, account, - 'connection_failed', path, array[0], array[1]) - - def unblock_signed_in_notifications(self, account): - gajim.block_signed_in_notifications[account] = False - - def handle_event_status(self, account, show): # OUR status - #('STATUS', account, show) - model = self.roster.status_combobox.get_model() - if show in ('offline', 'error'): - for name in self.instances[account]['online_dialog'].keys(): - # .keys() is needed to not have a dictionary length changed during - # iteration error - self.instances[account]['online_dialog'][name].destroy() - del self.instances[account]['online_dialog'][name] - for request in self.gpg_passphrase.values(): - if request: - request.interrupt() - # .keys() is needed because dict changes during loop - for account in self.pass_dialog.keys(): - self.pass_dialog[account].window.destroy() - if show == 'offline': - # sensitivity for this menuitem - if gajim.get_number_of_connected_accounts() == 0: - model[self.roster.status_message_menuitem_iter][3] = False - gajim.block_signed_in_notifications[account] = True - else: - # 30 seconds after we change our status to sth else than offline - # we stop blocking notifications of any kind - # this prevents from getting the roster items as 'just signed in' - # contacts. 30 seconds should be enough time - gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) - # sensitivity for this menuitem - model[self.roster.status_message_menuitem_iter][3] = True - - # Inform all controls for this account of the connection state change - ctrls = self.msg_win_mgr.get_controls() - if account in self.minimized_controls: - # Can not be the case when we remove account - ctrls += self.minimized_controls[account].values() - for ctrl in ctrls: - if ctrl.account == account: - if show == 'offline' or (show == 'invisible' and \ - gajim.connections[account].is_zeroconf): - ctrl.got_disconnected() - else: - # Other code rejoins all GCs, so we don't do it here - if not ctrl.type_id == message_control.TYPE_GC: - ctrl.got_connected() - if ctrl.parent_win: - ctrl.parent_win.redraw_tab(ctrl) - - self.roster.on_status_changed(account, show) - if account in self.show_vcard_when_connect and show not in ('offline', - 'error'): - self.edit_own_details(account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('AccountPresence', (show, account)) - - def edit_own_details(self, account): - jid = gajim.get_jid_from_account(account) - if 'profile' not in self.instances[account]: - self.instances[account]['profile'] = \ - profile_window.ProfileWindow(account) - gajim.connections[account].request_vcard(jid) - - def handle_event_notify(self, account, array): - # 'NOTIFY' (account, (jid, status, status message, resource, - # priority, # keyID, timestamp, contact_nickname)) - # - # Contact changed show - - # FIXME: Drop and rewrite... - - statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] - # Ignore invalid show - if array[1] not in statuss: - return - old_show = 0 - new_show = statuss.index(array[1]) - status_message = array[2] - jid = array[0].split('/')[0] - keyID = array[5] - contact_nickname = array[7] - - # Get the proper keyID - keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID) - - resource = array[3] - if not resource: - resource = '' - priority = array[4] - if gajim.jid_is_transport(jid): - # It must be an agent - ji = jid.replace('@', '') - else: - ji = jid - - highest = gajim.contacts. \ - get_contact_with_highest_priority(account, jid) - was_highest = (highest and highest.resource == resource) - - conn = gajim.connections[account] - - # Update contact - jid_list = gajim.contacts.get_jid_list(account) - if ji in jid_list or jid == gajim.get_jid_from_account(account): - lcontact = gajim.contacts.get_contacts(account, ji) - contact1 = None - resources = [] - for c in lcontact: - resources.append(c.resource) - if c.resource == resource: - contact1 = c - break - - if contact1: - if contact1.show in statuss: - old_show = statuss.index(contact1.show) - # nick changed - if contact_nickname is not None and \ - contact1.contact_name != contact_nickname: - contact1.contact_name = contact_nickname - self.roster.draw_contact(jid, account) - - if old_show == new_show and contact1.status == status_message and \ - contact1.priority == priority: # no change - return - else: - contact1 = gajim.contacts.get_first_contact_from_jid(account, ji) - if not contact1: - # Presence of another resource of our - # jid - # Create self contact and add to roster - if resource == conn.server_resource: - return - # Ignore offline presence of unknown self resource - if new_show < 2: - return - contact1 = gajim.contacts.create_contact(jid=ji, - name=gajim.nicks[account], groups=['self_contact'], - show=array[1], status=status_message, sub='both', ask='none', - priority=priority, keyID=keyID, resource=resource, - mood=conn.mood, tune=conn.tune, activity=conn.activity) - old_show = 0 - gajim.contacts.add_contact(account, contact1) - lcontact.append(contact1) - elif contact1.show in statuss: - old_show = statuss.index(contact1.show) - if (resources != [''] and (len(lcontact) != 1 or \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: - # Another resource of an existing contact connected - old_show = 0 - contact1 = gajim.contacts.copy_contact(contact1) - lcontact.append(contact1) - contact1.resource = resource - - self.roster.add_contact(contact1.jid, account) - - if contact1.jid.find('@') > 0 and len(lcontact) == 1: - # It's not an agent - if old_show == 0 and new_show > 1: - if not contact1.jid in gajim.newly_added[account]: - gajim.newly_added[account].append(contact1.jid) - if contact1.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].remove(contact1.jid) - gobject.timeout_add_seconds(5, self.roster.remove_newly_added, - contact1.jid, account) - elif old_show > 1 and new_show == 0 and conn.connected > 1: - if not contact1.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].append(contact1.jid) - if contact1.jid in gajim.newly_added[account]: - gajim.newly_added[account].remove(contact1.jid) - self.roster.draw_contact(contact1.jid, account) - gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, - contact1.jid, account) - - # unset custom status - if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\ - and conn.connected > 1): - if account in self.status_sent_to_users and \ - jid in self.status_sent_to_users[account]: - del self.status_sent_to_users[account][jid] - - contact1.show = array[1] - contact1.status = status_message - contact1.priority = priority - contact1.keyID = keyID - timestamp = array[6] - if timestamp: - contact1.last_status_time = timestamp - elif not gajim.block_signed_in_notifications[account]: - # We're connected since more that 30 seconds - contact1.last_status_time = time.localtime() - contact1.contact_nickname = contact_nickname - - if gajim.jid_is_transport(jid): - # It must be an agent - if ji in jid_list: - # Update existing iter and group counting - self.roster.draw_contact(ji, account) - self.roster.draw_group(_('Transports'), account) - if new_show > 1 and ji in gajim.transport_avatar[account]: - # transport just signed in. - # request avatars - for jid_ in gajim.transport_avatar[account][ji]: - conn.request_vcard(jid_) - # transport just signed in/out, don't show - # popup notifications for 30s - account_ji = account + '/' + ji - gajim.block_signed_in_notifications[account_ji] = True - gobject.timeout_add_seconds(30, - self.unblock_signed_in_notifications, account_ji) - locations = (self.instances, self.instances[account]) - for location in locations: - if 'add_contact' in location: - if old_show == 0 and new_show > 1: - location['add_contact'].transport_signed_in(jid) - break - elif old_show > 1 and new_show == 0: - location['add_contact'].transport_signed_out(jid) - break - elif ji in jid_list: - # It isn't an agent - # reset chatstate if needed: - # (when contact signs out or has errors) - if array[1] in ('offline', 'error'): - contact1.our_chatstate = contact1.chatstate = \ - contact1.composing_xep = None - - # TODO: This causes problems when another - # resource signs off! - conn.remove_transfers_for_contact(contact1) - - # disable encryption, since if any messages are - # lost they'll be not decryptable (note that - # this contradicts XEP-0201 - trying to get that - # in the XEP, though) - - # there won't be any sessions here if the contact terminated - # their sessions before going offline (which we do) - for sess in conn.get_sessions(ji): - if (ji+'/'+resource) != str(sess.jid): - continue - if sess.control: - sess.control.no_autonegotiation = False - if sess.enable_encryption: - sess.terminate_e2e() - conn.delete_session(jid, sess.thread_id) - - self.roster.chg_contact_status(contact1, array[1], status_message, - account) - # Notifications - if old_show < 2 and new_show > 1: - notify.notify('contact_connected', jid, account, status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactPresence', (account, - array)) - - elif old_show > 1 and new_show < 2: - notify.notify('contact_disconnected', jid, account, status_message) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactAbsence', (account, array)) - # FIXME: stop non active file transfers - # Status change (not connected/disconnected or - # error (<1)) - elif new_show > 1: - notify.notify('status_change', jid, account, [new_show, - status_message]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('ContactStatus', (account, array)) - else: - # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't - # follow the XEP, still the case in 2008. - # It's maybe a GC_NOTIFY (specialy for MSN gc) - self.handle_event_gc_notify(account, (jid, array[1], status_message, - array[3], None, None, None, None, None, [], None, None)) - - highest = gajim.contacts.get_contact_with_highest_priority(account, jid) - is_highest = (highest and highest.resource == resource) - - # disconnect the session from the ctrl if the highest resource has changed - if (was_highest and not is_highest) or (not was_highest and is_highest): - ctrl = self.msg_win_mgr.get_control(jid, account) - - if ctrl: - ctrl.no_autonegotiation = False - ctrl.set_session(None) - ctrl.contact = highest - - def handle_event_msgerror(self, account, array): - #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session])) - full_jid_with_resource = array[0] - jids = full_jid_with_resource.split('/', 1) - jid = jids[0] - - if array[1] == '503': - # If we get server-not-found error, stop sending chatstates - for contact in gajim.contacts.get_contacts(account, jid): - contact.composing_xep = False - - session = None - if len(array) > 5: - session = array[5] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - if gc_control and gc_control.type_id != message_control.TYPE_GC: - gc_control = None - if gc_control: - if len(jids) > 1: # it's a pm - nick = jids[1] - - if session: - ctrl = session.control - else: - ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) - - if not ctrl: - tv = gc_control.list_treeview - model = tv.get_model() - iter_ = gc_control.get_contact_iter(nick) - if iter_: - show = model[iter_][3] - else: - show = 'offline' - gc_c = gajim.contacts.create_gc_contact(room_jid = jid, - name = nick, show = show) - ctrl = self.new_private_chat(gc_c, account, session) - - ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - return - - gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { - 'code': array[1], 'msg': array[2]}, 'status') - if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid: - gc_control.set_subject(gc_control.subject) - return - - if gajim.jid_is_transport(jid): - jid = jid.replace('@', '') - msg = array[2] - if array[3]: - msg = _('error while sending %(message)s ( %(error)s )') % { - 'message': array[3], 'error': msg} - if session: - session.roster_message(jid, msg, array[4], msg_type='error') - - def handle_event_msgsent(self, account, array): - #('MSGSENT', account, (jid, msg, keyID)) - msg = array[1] - # do not play sound when standalone chatstate message (eg no msg) - if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): - helpers.play_sound('message_sent') - - def handle_event_msgnotsent(self, account, array): - #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) - msg = _('error while sending %(message)s ( %(error)s )') % { - 'message': array[2], 'error': array[1]} - if not array[4]: - # No session. This can happen when sending a message from gajim-remote - log.warn(msg) - return - array[4].roster_message(array[0], msg, array[3], account, - msg_type='error') - - def handle_event_subscribe(self, account, array): - #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Subscribe', (account, array)) - - jid = array[0] - text = array[1] - nick = array[2] - if helpers.allow_popup_window(account) or not self.systray_enabled: - dialogs.SubscriptionRequestWindow(jid, text, account, nick) - return - - self.add_event(account, jid, 'subscription_request', (text, nick)) - - if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'subscription_request.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - event_type = _('Subscription request') - notify.popup(event_type, jid, account, 'subscription_request', path, - event_type, jid) - - def handle_event_subscribed(self, account, array): - #('SUBSCRIBED', account, (jid, resource)) - jid = array[0] - if jid in gajim.contacts.get_jid_list(account): - c = gajim.contacts.get_first_contact_from_jid(account, jid) - c.resource = array[1] - self.roster.remove_contact_from_groups(c.jid, account, - [_('Not in Roster'), _('Observers')], update=False) - else: - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - name = jid.split('@', 1)[0] - name = name.split('%', 1)[0] - contact1 = gajim.contacts.create_contact(jid=jid, name=name, - groups=[], show='online', status='online', - ask='to', resource=array[1], keyID=keyID) - gajim.contacts.add_contact(account, contact1) - self.roster.add_contact(jid, account) - dialogs.InformationDialog(_('Authorization accepted'), - _('The contact "%s" has authorized you to see his or her status.') - % jid) - if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'): - gajim.connections[account].ack_subscribed(jid) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Subscribed', (account, array)) - - def show_unsubscribed_dialog(self, account, contact): - def on_yes(is_checked, list_): - self.roster.on_req_usub(None, list_) - list_ = [(contact, account)] - dialogs.YesNoDialog( - _('Contact "%s" removed subscription from you') % contact.jid, - _('You will always see him or her as offline.\nDo you want to ' - 'remove him or her from your contact list?'), - on_response_yes=(on_yes, list_)) - # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does - # not show deny - - def handle_event_unsubscribed(self, account, jid): - #('UNSUBSCRIBED', account, jid) - gajim.connections[account].ack_unsubscribed(jid) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) - - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - return - - if helpers.allow_popup_window(account) or not self.systray_enabled: - self.show_unsubscribed_dialog(account, contact) - - self.add_event(account, jid, 'unsubscribed', contact) - - if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'unsubscribed.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - event_type = _('Unsubscribed') - notify.popup(event_type, jid, account, 'unsubscribed', path, - event_type, jid) - - def handle_event_agent_info_error(self, account, agent): - #('AGENT_ERROR_INFO', account, (agent)) - try: - gajim.connections[account].services_cache.agent_info_error(agent) - except AttributeError: - return - - def handle_event_agent_items_error(self, account, agent): - #('AGENT_ERROR_INFO', account, (agent)) - try: - gajim.connections[account].services_cache.agent_items_error(agent) - except AttributeError: - return - - def handle_event_agent_removed(self, account, agent): - # remove transport's contacts from treeview - jid_list = gajim.contacts.get_jid_list(account) - for jid in jid_list: - if jid.endswith('@' + agent): - c = gajim.contacts.get_first_contact_from_jid(account, jid) - gajim.log.debug( - 'Removing contact %s due to unregistered transport %s'\ - % (jid, agent)) - gajim.connections[account].unsubscribe(c.jid) - # Transport contacts can't have 2 resources - if c.jid in gajim.to_be_removed[account]: - # This way we'll really remove it - gajim.to_be_removed[account].remove(c.jid) - self.roster.remove_contact(c.jid, account, backend=True) - - def handle_event_register_agent_info(self, account, array): - # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) - # info in a dataform if is_form is True - if array[2] or 'instructions' in array[1]: - config.ServiceRegistrationWindow(array[0], array[1], account, - array[2]) - else: - dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ - % array[0], _('Check your connection or try again later.')) - - def handle_event_agent_info_items(self, account, array): - #('AGENT_INFO_ITEMS', account, (agent, node, items)) - our_jid = gajim.get_jid_from_account(account) - if 'pep_services' in gajim.interface.instances[account] and \ - array[0] == our_jid: - gajim.interface.instances[account]['pep_services'].items_received( - array[2]) - try: - gajim.connections[account].services_cache.agent_items(array[0], - array[1], array[2]) - except AttributeError: - return - - def handle_event_agent_info_info(self, account, array): - #('AGENT_INFO_INFO', account, (agent, node, identities, features, data)) - try: - gajim.connections[account].services_cache.agent_info(array[0], - array[1], array[2], array[3], array[4]) - except AttributeError: - return - - def handle_event_new_acc_connected(self, account, array): - #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err, - # ssl_cert, ssl_fingerprint)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].new_acc_connected(array[0], - array[1], array[2], array[3], array[4], array[5]) - - def handle_event_new_acc_not_connected(self, account, array): - #('NEW_ACC_NOT_CONNECTED', account, (reason)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].new_acc_not_connected(array) - - def handle_event_acc_ok(self, account, array): - #('ACC_OK', account, (config)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].acc_is_ok(array) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('NewAccount', (account, array)) - - def handle_event_acc_not_ok(self, account, array): - #('ACC_NOT_OK', account, (reason)) - if 'account_creation_wizard' in self.instances: - self.instances['account_creation_wizard'].acc_is_not_ok(array) - - def handle_event_quit(self, p1, p2): - self.roster.quit_gtkgui_interface() - - def handle_event_myvcard(self, account, array): - nick = '' - if 'NICKNAME' in array and array['NICKNAME']: - gajim.nicks[account] = array['NICKNAME'] - elif 'FN' in array and array['FN']: - gajim.nicks[account] = array['FN'] - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.set_values(array) - if account in self.show_vcard_when_connect: - self.show_vcard_when_connect.remove(account) - jid = array['jid'] - if jid in self.instances[account]['infos']: - self.instances[account]['infos'][jid].set_values(array) - - def handle_event_vcard(self, account, vcard): - # ('VCARD', account, data) - '''vcard holds the vcard data''' - jid = vcard['jid'] - resource = vcard.get('resource', '') - fjid = jid + '/' + str(resource) - - # vcard window - win = None - if jid in self.instances[account]['infos']: - win = self.instances[account]['infos'][jid] - elif resource and fjid in self.instances[account]['infos']: - win = self.instances[account]['infos'][fjid] - if win: - win.set_values(vcard) - - # show avatar in chat - ctrl = None - if resource and self.msg_win_mgr.has_window(fjid, account): - win = self.msg_win_mgr.get_window(fjid, account) - ctrl = win.get_control(fjid, account) - elif self.msg_win_mgr.has_window(jid, account): - win = self.msg_win_mgr.get_window(jid, account) - ctrl = win.get_control(jid, account) - - if ctrl and ctrl.type_id != message_control.TYPE_GC: - ctrl.show_avatar() - - # Show avatar in roster or gc_roster - gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_ctrl and \ - jid in self.minimized_controls[account]: - gc_ctrl = self.minimized_controls[account][jid] - if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: - gc_ctrl.draw_avatar(resource) - else: - self.roster.draw_avatar(jid, account) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) - - def handle_event_last_status_time(self, account, array): - # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) - tim = array[2] - if tim < 0: - # Ann error occured - return - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - c = gajim.contacts.get_contact(account, array[0], array[1]) - if c: # c can be none if it's a gc contact - c.last_status_time = time.localtime(time.time() - tim) - if array[3]: - c.status = array[3] - self.roster.draw_contact(c.jid, account) # draw offline status - if win: - win.set_last_status_time() - if self.remote_ctrl: - self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) - - def handle_event_os_info(self, account, array): - #'OS_INFO' (account, (jid, resource, client_info, os_info)) - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - if win: - win.set_os_info(array[1], array[2], array[3]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('OsInfo', (account, array)) - - def handle_event_entity_time(self, account, array): - #'ENTITY_TIME' (account, (jid, resource, time_info)) - win = None - if array[0] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0]] - elif array[0] + '/' + array[1] in self.instances[account]['infos']: - win = self.instances[account]['infos'][array[0] + '/' + array[1]] - if win: - win.set_entity_time(array[1], array[2]) - if self.remote_ctrl: - self.remote_ctrl.raise_signal('EntityTime', (account, array)) - - def handle_event_gc_notify(self, account, array): - #'GC_NOTIFY' (account, (room_jid, show, status, nick, - # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha)) - nick = array[3] - if not nick: - return - room_jid = array[0] - fjid = room_jid + '/' + nick - show = array[1] - status = array[2] - conn = gajim.connections[account] - - # Get the window and control for the updated status, this may be a - # PrivateChatControl - control = self.msg_win_mgr.get_gc_control(room_jid, account) - - if not control and \ - room_jid in self.minimized_controls[account]: - control = self.minimized_controls[account][room_jid] - - if not control or (control and control.type_id != message_control.TYPE_GC): - return - - control.chg_contact_status(nick, show, status, array[4], array[5], - array[6], array[7], array[8], array[9], array[10], array[11]) - - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, room_jid) - if contact: - self.roster.draw_contact(room_jid, account) - - # print status in chat window and update status/GPG image - ctrl = self.msg_win_mgr.get_control(fjid, account) - if ctrl: - statusCode = array[9] - if '303' in statusCode: - new_nick = array[10] - ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \ - % {'nick': nick, 'new_nick': new_nick}, 'status') - gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick) - c = gajim.contacts.contact_from_gc_contact(gc_c) - ctrl.gc_contact = gc_c - ctrl.contact = c - if ctrl.session: - # stop e2e - if ctrl.session.enable_encryption: - thread_id = ctrl.session.thread_id - ctrl.session.terminate_e2e() - conn.delete_session(fjid, thread_id) - ctrl.no_autonegotiation = False - ctrl.draw_banner() - old_jid = room_jid + '/' + nick - new_jid = room_jid + '/' + new_nick - self.msg_win_mgr.change_key(old_jid, new_jid, account) - else: - contact = ctrl.contact - contact.show = show - contact.status = status - gc_contact = ctrl.gc_contact - gc_contact.show = show - gc_contact.status = status - uf_show = helpers.get_uf_show(show) - ctrl.print_conversation(_('%(nick)s is now %(status)s') % { - 'nick': nick, 'status': uf_show}, 'status') - if status: - ctrl.print_conversation(' (', 'status', simple=True) - ctrl.print_conversation('%s' % (status), 'status', simple=True) - ctrl.print_conversation(')', 'status', simple=True) - ctrl.parent_win.redraw_tab(ctrl) - ctrl.update_ui() - if self.remote_ctrl: - self.remote_ctrl.raise_signal('GCPresence', (account, array)) - - def handle_event_gc_msg(self, account, array): - # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, - # [status_codes])) - jids = array[0].split('/', 1) - room_jid = jids[0] - - msg = array[1] - - gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) - if not gc_control and \ - room_jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][room_jid] - - if not gc_control: - return - xhtml = array[4] - - if gajim.config.get('ignore_incoming_xhtml'): - xhtml = None - if len(jids) == 1: - # message from server - nick = '' - else: - # message from someone - nick = jids[1] - - gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5]) - - if self.remote_ctrl: - highlight = gc_control.needs_visual_notification(msg) - array += (highlight,) - self.remote_ctrl.raise_signal('GCMessage', (account, array)) - - def handle_event_gc_subject(self, account, array): - #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) - jids = array[0].split('/', 1) - jid = jids[0] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - - contact = gajim.contacts.\ - get_contact_with_highest_priority(account, jid) - if contact: - contact.status = array[1] - self.roster.draw_contact(jid, account) - - if not gc_control: - return - gc_control.set_subject(array[1]) - # Standard way, the message comes from the occupant who set the subject - text = None - if len(jids) > 1: - text = _('%(jid)s has set the subject to %(subject)s') % { - 'jid': jids[1], 'subject': array[1]} - # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be - # deleted one day. We can receive a subject with a body that contains - # "X has set the subject to Y" ... - elif array[2]: - text = array[2] - if text is not None: - if array[3]: - gc_control.print_old_conversation(text) - else: - gc_control.print_conversation(text) - - def handle_event_gc_config(self, account, array): - #('GC_CONFIG', account, (jid, form)) config is a dict - room_jid = array[0].split('/')[0] - if room_jid in gajim.automatic_rooms[account]: - if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: - # We're converting chat to muc. allow participants to invite - form = dataforms.ExtendForm(node = array[1]) - for f in form.iter_fields(): - if f.var == 'muc#roomconfig_allowinvites': - f.value = True - elif f.var == 'muc#roomconfig_publicroom': - f.value = False - elif f.var == 'muc#roomconfig_membersonly': - f.value = True - elif f.var == 'public_list': - f.value = False - gajim.connections[account].send_gc_config(room_jid, form) - else: - # use default configuration - gajim.connections[account].send_gc_config(room_jid, array[1]) - # invite contacts - # check if it is necessary to add - continue_tag = False - if 'continue_tag' in gajim.automatic_rooms[account][room_jid]: - continue_tag = True - if 'invities' in gajim.automatic_rooms[account][room_jid]: - for jid in gajim.automatic_rooms[account][room_jid]['invities']: - gajim.connections[account].send_invite(room_jid, jid, - continue_tag=continue_tag) - del gajim.automatic_rooms[account][room_jid] - elif room_jid not in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][room_jid] = \ - config.GroupchatConfigWindow(account, room_jid, array[1]) - - def handle_event_gc_config_change(self, account, array): - #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init - jid = array[0] - statusCode = array[1] - - gc_control = self.msg_win_mgr.get_gc_control(jid, account) - if not gc_control and \ - jid in self.minimized_controls[account]: - gc_control = self.minimized_controls[account][jid] - if not gc_control: - return - - changes = [] - if '100' in statusCode: - # Can be a presence (see chg_contact_status in groupchat_control.py) - changes.append(_('Any occupant is allowed to see your full JID')) - gc_control.is_anonymous = False - if '102' in statusCode: - changes.append(_('Room now shows unavailable member')) - if '103' in statusCode: - changes.append(_('room now does not show unavailable members')) - if '104' in statusCode: - changes.append( - _('A non-privacy-related room configuration change has occurred')) - if '170' in statusCode: - # Can be a presence (see chg_contact_status in groupchat_control.py) - changes.append(_('Room logging is now enabled')) - if '171' in statusCode: - changes.append(_('Room logging is now disabled')) - if '172' in statusCode: - changes.append(_('Room is now non-anonymous')) - gc_control.is_anonymous = False - if '173' in statusCode: - changes.append(_('Room is now semi-anonymous')) - gc_control.is_anonymous = True - if '174' in statusCode: - changes.append(_('Room is now fully-anonymous')) - gc_control.is_anonymous = True - - for change in changes: - gc_control.print_conversation(change) - - def handle_event_gc_affiliation(self, account, array): - #('GC_AFFILIATION', account, (room_jid, users_dict)) - room_jid = array[0] - if room_jid in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][room_jid].\ - affiliation_list_received(array[1]) - - def handle_event_gc_password_required(self, account, array): - #('GC_PASSWORD_REQUIRED', account, (room_jid, nick)) - room_jid = array[0] - nick = array[1] - - def on_ok(text): - gajim.connections[account].join_gc(nick, room_jid, text) - gajim.gc_passwords[room_jid] = text - - def on_cancel(): - # get and destroy window - if room_jid in gajim.interface.minimized_controls[account]: - self.roster.on_disconnect(None, room_jid, account) - else: - win = self.msg_win_mgr.get_window(room_jid, account) - ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) - win.remove_tab(ctrl, 3) - - dlg = dialogs.InputDialog(_('Password Required'), - _('A Password is required to join the room %s. Please type it.') % \ - room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) - dlg.input_entry.set_visibility(False) - - def handle_event_gc_invitation(self, account, array): - #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) - jid = gajim.get_jid_without_resource(array[1]) - room_jid = array[0] - if helpers.allow_popup_window(account) or not self.systray_enabled: - dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3], - array[2], is_continued=array[4]) - return - - self.add_event(account, jid, 'gc-invitation', (room_jid, array[2], - array[3], array[4])) - - if helpers.allow_showing_notification(account): - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'gc_invitation.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(path) - event_type = _('Groupchat Invitation') - notify.popup(event_type, jid, account, 'gc-invitation', path, - event_type, room_jid) - - def forget_gpg_passphrase(self, keyid): - if keyid in self.gpg_passphrase: - del self.gpg_passphrase[keyid] - return False - - def handle_event_bad_passphrase(self, account, array): - #('BAD_PASSPHRASE', account, ()) - use_gpg_agent = gajim.config.get('use_gpg_agent') - sectext = '' - if use_gpg_agent: - sectext = _('You configured Gajim to use GPG agent, but there is no ' - 'GPG agent running or it returned a wrong passphrase.\n') - sectext += _('You are currently connected without your OpenPGP key.') - dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) - else: - path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'warning.png') - notify.popup('warning', account, account, 'warning', path, - _('OpenGPG Passphrase Incorrect'), - _('You are currently connected without your OpenPGP key.')) - keyID = gajim.config.get_per('accounts', account, 'keyid') - self.forget_gpg_passphrase(keyID) - - def handle_event_gpg_password_required(self, account, array): - #('GPG_PASSWORD_REQUIRED', account, (callback,)) - callback = array[0] - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid in self.gpg_passphrase: - request = self.gpg_passphrase[keyid] - else: - request = PassphraseRequest(keyid) - self.gpg_passphrase[keyid] = request - request.add_callback(account, callback) - - def handle_event_gpg_always_trust(self, account, callback): - #('GPG_ALWAYS_TRUST', account, callback) - def on_yes(checked): - if checked: - gajim.connections[account].gpg.always_trust = True - callback(True) - - def on_no(): - callback(False) - - dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' - 'encrypt this chat is not trusted. Do you really want to encrypt this ' - 'message?'), checktext=_('Do _not ask me again'), - on_response_yes=on_yes, on_response_no=on_no) - - def handle_event_password_required(self, account, array): - #('PASSWORD_REQUIRED', account, None) - if account in self.pass_dialog: - return - text = _('Enter your password for account %s') % account - if passwords.USER_HAS_GNOMEKEYRING and \ - not passwords.USER_USES_GNOMEKEYRING: - text += '\n' + _('Gnome Keyring is installed but not \ - correctly started (environment variable probably not \ - correctly set)') - - def on_ok(passphrase, save): - if save: - gajim.config.set_per('accounts', account, 'savepass', True) - passwords.save_password(account, passphrase) - gajim.connections[account].set_password(passphrase) - del self.pass_dialog[account] - - def on_cancel(): - self.roster.set_state(account, 'offline') - self.roster.update_status_combobox() - del self.pass_dialog[account] - - self.pass_dialog[account] = dialogs.PassphraseDialog( - _('Password Required'), text, _('Save password'), ok_handler=on_ok, - cancel_handler=on_cancel) - - def handle_event_roster_info(self, account, array): - #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) - jid = array[0] - name = array[1] - sub = array[2] - ask = array[3] - groups = array[4] - contacts = gajim.contacts.get_contacts(account, jid) - if (not sub or sub == 'none') and (not ask or ask == 'none') and \ - not name and not groups: - # contact removed us. - if contacts: - self.roster.remove_contact(jid, account, backend=True) - return - elif not contacts: - if sub == 'remove': - return - # Add new contact to roster - contact = gajim.contacts.create_contact(jid=jid, name=name, - groups=groups, show='offline', sub=sub, ask=ask) - gajim.contacts.add_contact(account, contact) - self.roster.add_contact(jid, account) - else: - # it is an existing contact that might has changed - re_place = False - # If contact has changed (sub, ask or group) update roster - # Mind about observer status changes: - # According to xep 0162, a contact is not an observer anymore when - # we asked for auth, so also remove him if ask changed - old_groups = contacts[0].groups - if contacts[0].sub != sub or contacts[0].ask != ask\ - or old_groups != groups: - re_place = True - # c.get_shown_groups() has changed. Reflect that in roster_winodow - self.roster.remove_contact(jid, account, force=True) - for contact in contacts: - contact.name = name or '' - contact.sub = sub - contact.ask = ask - contact.groups = groups or [] - if re_place: - self.roster.add_contact(jid, account) - # Refilter and update old groups - for group in old_groups: - self.roster.draw_group(group, account) - else: - self.roster.draw_contact(jid, account) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('RosterInfo', (account, array)) - - def handle_event_bookmarks(self, account, bms): - # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) - # We received a bookmark item from the server (JEP48) - # Auto join GC windows if neccessary - - self.roster.set_actions_menu_needs_rebuild() - invisible_show = gajim.SHOW_LIST.index('invisible') - # do not autojoin if we are invisible - if gajim.connections[account].connected == invisible_show: - return - - self.auto_join_bookmarks(account) - - def handle_event_file_send_error(self, account, array): - jid = array[0] - file_props = array[1] - ft = self.instances['file_transfers'] - ft.set_status(file_props['type'], file_props['sid'], 'stop') - - if helpers.allow_popup_window(account): - ft.show_send_error(file_props) - return - - self.add_event(account, jid, 'file-send-error', file_props) - - if helpers.allow_showing_notification(account): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('File Transfer Error') - notify.popup(event_type, jid, account, 'file-send-error', path, - event_type, file_props['name']) - - def handle_event_gmail_notify(self, account, array): - jid = array[0] - gmail_new_messages = int(array[1]) - gmail_messages_list = array[2] - if gajim.config.get('notify_on_new_gmail_email'): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'new_email_recv.png') - title = _('New mail on %(gmail_mail_address)s') % \ - {'gmail_mail_address': jid} - text = i18n.ngettext('You have %d new mail conversation', - 'You have %d new mail conversations', gmail_new_messages, - gmail_new_messages, gmail_new_messages) - - if gajim.config.get('notify_on_new_gmail_email_extra'): - cnt = 0 - for gmessage in gmail_messages_list: - #FIXME: emulate Gtalk client popups. find out what they parse and - # how they decide what to show each message has a 'From', - # 'Subject' and 'Snippet' field - if cnt >=5: - break - senders = ',\n '.join(reversed(gmessage['From'])) - text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \ - {'from_address': senders, 'subject': gmessage['Subject'], - 'snippet': gmessage['Snippet']} - cnt += 1 - - if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): - helpers.play_sound('gmail_received') - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - notify.popup(_('New E-mail'), jid, account, 'gmail', - path_to_image=path, title=title, - text=text) - - if self.remote_ctrl: - self.remote_ctrl.raise_signal('NewGmail', (account, array)) - - def handle_event_file_request_error(self, account, array): - # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) - jid, file_props, errmsg = array - ft = self.instances['file_transfers'] - ft.set_status(file_props['type'], file_props['sid'], 'stop') - errno = file_props['error'] - - if helpers.allow_popup_window(account): - if errno in (-4, -5): - ft.show_stopped(jid, file_props, errmsg) - else: - ft.show_request_error(file_props) - return - - if errno in (-4, -5): - msg_type = 'file-error' - else: - msg_type = 'file-request-error' - - self.add_event(account, jid, msg_type, file_props) - - if helpers.allow_showing_notification(account): - # check if we should be notified - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png') - - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('File Transfer Error') - notify.popup(event_type, jid, account, msg_type, path, - title = event_type, text = file_props['name']) - - def handle_event_file_request(self, account, array): - jid = array[0] - if jid not in gajim.contacts.get_jid_list(account): - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid=jid, name='', - groups=[_('Not in Roster')], show='not in roster', status='', - sub='none', keyID=keyID) - gajim.contacts.add_contact(account, contact) - self.roster.add_contact(contact.jid, account) - file_props = array[1] - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - - if helpers.allow_popup_window(account): - self.instances['file_transfers'].show_file_request(account, contact, - file_props) - return - - self.add_event(account, jid, 'file-request', file_props) - - if helpers.allow_showing_notification(account): - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'ft_request.png') - txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( - account, jid) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - event_type = _('File Transfer Request') - notify.popup(event_type, jid, account, 'file-request', - path_to_image = path, title = event_type, text = txt) - - def handle_event_file_error(self, title, message): - dialogs.ErrorDialog(title, message) - - def handle_event_file_progress(self, account, file_props): - if time.time() - self.last_ftwindow_update > 0.5: - # update ft window every 500ms - self.last_ftwindow_update = time.time() - self.instances['file_transfers'].set_progress(file_props['type'], - file_props['sid'], file_props['received-len']) - - def handle_event_file_rcv_completed(self, account, file_props): - ft = self.instances['file_transfers'] - if file_props['error'] == 0: - ft.set_progress(file_props['type'], file_props['sid'], - file_props['received-len']) - else: - ft.set_status(file_props['type'], file_props['sid'], 'stop') - if 'stalled' in file_props and file_props['stalled'] or \ - 'paused' in file_props and file_props['paused']: - return - if file_props['type'] == 'r': # we receive a file - jid = unicode(file_props['sender']) - else: # we send a file - jid = unicode(file_props['receiver']) - - if helpers.allow_popup_window(account): - if file_props['error'] == 0: - if gajim.config.get('notify_on_file_complete'): - ft.show_completed(jid, file_props) - elif file_props['error'] == -1: - ft.show_stopped(jid, file_props, - error_msg=_('Remote contact stopped transfer')) - elif file_props['error'] == -6: - ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) - return - - msg_type = '' - event_type = '' - if file_props['error'] == 0 and gajim.config.get( - 'notify_on_file_complete'): - msg_type = 'file-completed' - event_type = _('File Transfer Completed') - elif file_props['error'] in (-1, -6): - msg_type = 'file-stopped' - event_type = _('File Transfer Stopped') - - if event_type == '': - # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) - # this should never happen but it does. see process_result() in socks5.py - # who calls this func (sth is really wrong unless this func is also registered - # as progress_cb - return - - if msg_type: - self.add_event(account, jid, msg_type, file_props) - - if file_props is not None: - if file_props['type'] == 'r': - # get the name of the sender, as it is in the roster - sender = unicode(file_props['sender']).split('/')[0] - name = gajim.contacts.get_first_contact_from_jid(account, - sender).get_shown_name() - filename = os.path.basename(file_props['file-name']) - if event_type == _('File Transfer Completed'): - txt = _('You successfully received %(filename)s from %(name)s.')\ - % {'filename': filename, 'name': name} - img = 'ft_done.png' - else: # ft stopped - txt = _('File transfer of %(filename)s from %(name)s stopped.')\ - % {'filename': filename, 'name': name} - img = 'ft_stopped.png' - else: - receiver = file_props['receiver'] - if hasattr(receiver, 'jid'): - receiver = receiver.jid - receiver = receiver.split('/')[0] - # get the name of the contact, as it is in the roster - name = gajim.contacts.get_first_contact_from_jid(account, - receiver).get_shown_name() - filename = os.path.basename(file_props['file-name']) - if event_type == _('File Transfer Completed'): - txt = _('You successfully sent %(filename)s to %(name)s.')\ - % {'filename': filename, 'name': name} - img = 'ft_done.png' - else: # ft stopped - txt = _('File transfer of %(filename)s to %(name)s stopped.')\ - % {'filename': filename, 'name': name} - img = 'ft_stopped.png' - img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img) - path = gtkgui_helpers.get_path_to_generic_or_avatar(img) - else: - txt = '' - - if gajim.config.get('notify_on_file_complete') and \ - (gajim.config.get('autopopupaway') or \ - gajim.connections[account].connected in (2, 3)): - # we want to be notified and we are online/chat or we don't mind - # bugged when away/na/busy - notify.popup(event_type, jid, account, msg_type, path_to_image = path, - title = event_type, text = txt) - - def handle_event_stanza_arrived(self, account, stanza): - if account not in self.instances: - return - if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'incoming') - - def handle_event_stanza_sent(self, account, stanza): - if account not in self.instances: - return - if 'xml_console' in self.instances[account]: - self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') - - def handle_event_vcard_published(self, account, array): - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.vcard_published() - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \ - self.minimized_controls[account].values(): - if gc_control.account == account: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - status = gajim.connections[account].status - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, show, status) - - def handle_event_vcard_not_published(self, account, array): - if 'profile' in self.instances[account]: - win = self.instances[account]['profile'] - win.vcard_not_published() - - def ask_offline_status(self, account): - for contact in gajim.contacts.iter_contacts(account): - gajim.connections[account].request_last_status_time(contact.jid, - contact.resource) - - def handle_event_signed_in(self, account, empty): - '''SIGNED_IN event is emitted when we sign in, so handle it''' - # block signed in notifications for 30 seconds - gajim.block_signed_in_notifications[account] = True - self.roster.set_actions_menu_needs_rebuild() - self.roster.draw_account(account) - state = self.sleeper.getState() - connected = gajim.connections[account].connected - if gajim.config.get('ask_offline_status_on_connection'): - # Ask offline status in 1 minute so w'are sure we got all online - # presences - gobject.timeout_add_seconds(60, self.ask_offline_status, account) - if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): - # we go online or free for chat, so we activate auto status - gajim.sleeper_state[account] = 'online' - elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ - (state == common.sleepy.STATE_XA and connected == 5)): - # If we are autoaway/xa and come back after a disconnection, do nothing - # Else disable autoaway - gajim.sleeper_state[account] = 'off' - invisible_show = gajim.SHOW_LIST.index('invisible') - # We cannot join rooms if we are invisible - if gajim.connections[account].connected == invisible_show: - return - # join already open groupchats - for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \ - + self.minimized_controls[account].values(): - if account != gc_control.account: - continue - room_jid = gc_control.room_jid - if room_jid in gajim.gc_connected[account] and \ - gajim.gc_connected[account][room_jid]: - continue - nick = gc_control.nick - password = gajim.gc_passwords.get(room_jid, '') - gajim.connections[account].join_gc(nick, room_jid, password) - - def handle_event_metacontacts(self, account, tags_list): - gajim.contacts.define_metacontacts(account, tags_list) - self.roster.redraw_metacontacts(account) - - def handle_atom_entry(self, account, data): - atom_entry, = data - AtomWindow.newAtomEntry(atom_entry) - - def handle_event_failed_decrypt(self, account, data): - jid, tim, session = data - - details = _('Unable to decrypt message from ' - '%s\nIt may have been tampered with.') % jid - - ctrl = session.control - if ctrl: - ctrl.print_conversation_line(details, 'status', '', tim) - else: - dialogs.WarningDialog(_('Unable to decrypt message'), - details) - - # terminate the session - session.terminate_e2e() - session.conn.delete_session(jid, session.thread_id) - - # restart the session - if ctrl: - ctrl.begin_e2e_negotiation() - - def handle_event_privacy_lists_received(self, account, data): - # ('PRIVACY_LISTS_RECEIVED', account, list) - if account not in self.instances: - return - if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_lists_received(data) - - def handle_event_privacy_list_received(self, account, data): - # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) - if account not in self.instances: - return - name = data[0] - rules = data[1] - if 'privacy_list_%s' % name in self.instances[account]: - self.instances[account]['privacy_list_%s' % name].\ - privacy_list_received(rules) - if name == 'block': - gajim.connections[account].blocked_contacts = [] - gajim.connections[account].blocked_groups = [] - gajim.connections[account].blocked_list = [] - gajim.connections[account].blocked_all = False - for rule in rules: - if not 'type' in rule: - gajim.connections[account].blocked_all = True - elif rule['type'] == 'jid' and rule['action'] == 'deny': - gajim.connections[account].blocked_contacts.append(rule['value']) - elif rule['type'] == 'group' and rule['action'] == 'deny': - gajim.connections[account].blocked_groups.append(rule['value']) - gajim.connections[account].blocked_list.append(rule) - #elif rule['type'] == "group" and action == "deny": - # text_item = _('%s group "%s"') % _(rule['action']), rule['value'] - # self.store.append([text_item]) - # self.global_rules.append(rule) - #else: - # self.global_rules_to_append.append(rule) - if 'blocked_contacts' in self.instances[account]: - self.instances[account]['blocked_contacts'].\ - privacy_list_received(rules) - - def handle_event_privacy_lists_active_default(self, account, data): - if not data: - return - # Send to all privacy_list_* windows as we can't know which one asked - for win in self.instances[account]: - if win.startswith('privacy_list_'): - self.instances[account][win].check_active_default(data) - - def handle_event_privacy_list_removed(self, account, name): - # ('PRIVACY_LISTS_REMOVED', account, name) - if account not in self.instances: - return - if 'privacy_lists' in self.instances[account]: - self.instances[account]['privacy_lists'].privacy_list_removed(name) - - def handle_event_zc_name_conflict(self, account, data): - def on_ok(new_name): - gajim.config.set_per('accounts', account, 'name', new_name) - status = gajim.connections[account].status - gajim.connections[account].username = new_name - gajim.connections[account].change_status(status, '') - def on_cancel(): - gajim.connections[account].change_status('offline','') - - dlg = dialogs.InputDialog(_('Username Conflict'), - _('Please type a new username for your local account'), input_str=data, - is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) - - def handle_event_ping_sent(self, account, contact): - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Ping?'), 'status') - - def handle_event_ping_reply(self, account, data): - contact = data[0] - seconds = data[1] - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status') - - def handle_event_ping_error(self, account, contact): - if contact.jid == contact.get_full_jid(): - # If contact is a groupchat user - jids = [contact.jid] - else: - jids = [contact.jid, contact.get_full_jid()] - for jid in jids: - ctrl = self.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.print_conversation(_('Error.'), 'status') - - def handle_event_search_form(self, account, data): - # ('SEARCH_FORM', account, (jid, dataform, is_dataform)) - if data[0] not in self.instances[account]['search']: - return - self.instances[account]['search'][data[0]].on_form_arrived(data[1], - data[2]) - - def handle_event_search_result(self, account, data): - # ('SEARCH_RESULT', account, (jid, dataform, is_dataform)) - if data[0] not in self.instances[account]['search']: - return - self.instances[account]['search'][data[0]].on_result_arrived(data[1], - data[2]) - - def handle_event_resource_conflict(self, account, data): - # ('RESOURCE_CONFLICT', account, ()) - # First we go offline, but we don't overwrite status message - self.roster.send_status(account, 'offline', - gajim.connections[account].status) - def on_ok(new_resource): - gajim.config.set_per('accounts', account, 'resource', new_resource) - self.roster.send_status(account, gajim.connections[account].old_show, - gajim.connections[account].status) - dlg = dialogs.InputDialog(_('Resource Conflict'), - _('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource, - is_modal = False, ok_handler = on_ok) - - def handle_event_pep_config(self, account, data): - # ('PEP_CONFIG', account, (node, form)) - if 'pep_services' in self.instances[account]: - self.instances[account]['pep_services'].config(data[0], data[1]) - - def handle_event_roster_item_exchange(self, account, data): - # data = (action in [add, delete, modify], exchange_list, jid_from) - dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2]) - - def handle_event_unique_room_id_supported(self, account, data): - '''Receive confirmation that unique_room_id are supported''' - # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) - instance = data[1] - instance.unique_room_id_supported(data[0], data[2]) - - def handle_event_unique_room_id_unsupported(self, account, data): - # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) - instance = data[1] - instance.unique_room_id_error(data[0]) - - def handle_event_ssl_error(self, account, data): - # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) - server = gajim.config.get_per('accounts', account, 'hostname') - - def on_ok(is_checked): - del self.instances[account]['online_dialog']['ssl_error'] - if is_checked[0]: - # Check if cert is already in file - certs = '' - if os.path.isfile(gajim.MY_CACERTS): - f = open(gajim.MY_CACERTS) - certs = f.read() - f.close() - if data[2] in certs: - dialogs.ErrorDialog(_('Certificate Already in File'), - _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS) - else: - f = open(gajim.MY_CACERTS, 'a') - f.write(server + '\n') - f.write(data[2] + '\n\n') - f.close() - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[3]) - if is_checked[1]: - ignore_ssl_errors = gajim.config.get_per('accounts', account, - 'ignore_ssl_errors').split() - ignore_ssl_errors.append(str(data[1])) - gajim.config.set_per('accounts', account, 'ignore_ssl_errors', - ' '.join(ignore_ssl_errors)) - gajim.connections[account].ssl_certificate_accepted() - - def on_cancel(): - del self.instances[account]['online_dialog']['ssl_error'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - - pritext = _('Error verifying SSL certificate') - sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]} - if data[1] in (18, 27): - checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] - else: - checktext1 = '' - checktext2 = _('Ignore this error for this certificate.') - if 'ssl_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['ssl_error'].destroy() - self.instances[account]['online_dialog']['ssl_error'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1, - checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) - - def handle_event_fingerprint_error(self, account, data): - # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) - def on_yes(is_checked): - del self.instances[account]['online_dialog']['fingerprint_error'] - gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', - data[0]) - # Reset the ignored ssl errors - gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') - gajim.connections[account].ssl_certificate_accepted() - def on_no(): - del self.instances[account]['online_dialog']['fingerprint_error'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('SSL certificate error') - sectext = _('It seems the SSL certificate of account %(account)s has ' - 'changed or your connection is being hacked.\nOld fingerprint: %(old)s' - '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update' - ' the fingerprint of the certificate?') % {'account': account, - 'old': gajim.config.get_per('accounts', account, - 'ssl_fingerprint_sha1'), 'new': data[0]} - if 'fingerprint_error' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['fingerprint_error'].destroy() - self.instances[account]['online_dialog']['fingerprint_error'] = \ - dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, - on_response_no=on_no) - - def handle_event_plain_connection(self, account, data): - # ('PLAIN_CONNECTION', account, (connection)) - server = gajim.config.get_per('accounts', account, 'hostname') - def on_ok(is_checked): - if not is_checked[0]: - on_cancel() - return - # On cancel call del self.instances, so don't call it another time - # before - del self.instances[account]['online_dialog']['plain_connection'] - if is_checked[1]: - gajim.config.set_per('accounts', account, - 'warn_when_plaintext_connection', False) - gajim.connections[account].connection_accepted(data[0], 'plain') - def on_cancel(): - del self.instances[account]['online_dialog']['plain_connection'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('Insecure connection') - sectext = _('You are about to send your password on an unencrypted ' - 'connection. Are you sure you want to do that?') - checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') - if 'plain_connection' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['plain_connection'].destroy() - self.instances[account]['online_dialog']['plain_connection'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) - - def handle_event_insecure_ssl_connection(self, account, data): - # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) - server = gajim.config.get_per('accounts', account, 'hostname') - def on_ok(is_checked): - del self.instances[account]['online_dialog']['insecure_ssl'] - if not is_checked[0]: - on_cancel() - return - if is_checked[1]: - gajim.config.set_per('accounts', account, - 'warn_when_insecure_ssl_connection', False) - if gajim.connections[account].connected == 0: - # We have been disconnecting (too long time since window is opened) - # re-connect with auto-accept - gajim.connections[account].connection_auto_accepted = True - show, msg = gajim.connections[account].continue_connect_info[:2] - self.roster.send_status(account, show, msg) - return - gajim.connections[account].connection_accepted(data[0], data[1]) - def on_cancel(): - del self.instances[account]['online_dialog']['insecure_ssl'] - gajim.connections[account].disconnect(on_purpose=True) - self.handle_event_status(account, 'offline') - pritext = _('Insecure connection') - sectext = _('You are about to send your password on an insecure ' - 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?') - checktext1 = _('Yes, I really want to connect insecurely') - checktext2 = _('Do _not ask me again') - if 'insecure_ssl' in self.instances[account]['online_dialog']: - self.instances[account]['online_dialog']['insecure_ssl'].destroy() - self.instances[account]['online_dialog']['insecure_ssl'] = \ - dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, - checktext1, checktext2, on_response_ok=on_ok, - on_response_cancel=on_cancel, is_modal=False) - - def handle_event_pubsub_node_removed(self, account, data): - # ('PUBSUB_NODE_REMOVED', account, (jid, node)) - if 'pep_services' in self.instances[account]: - if data[0] == gajim.get_jid_from_account(account): - self.instances[account]['pep_services'].node_removed(data[1]) - - def handle_event_pubsub_node_not_removed(self, account, data): - # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg)) - if data[0] == gajim.get_jid_from_account(account): - dialogs.WarningDialog(_('PEP node was not removed'), - _('PEP node %(node)s was not removed: %(message)s') % { - 'node': data[1], 'message': data[2]}) - - def register_handler(self, event, handler): - if event not in self.handlers: - self.handlers[event] = [] - - if handler not in self.handlers[event]: - self.handlers[event].append(handler) - - def unregister_handler(self, event, handler): - self.handlers[event].remove(handler) - - def register_handlers(self): - self.handlers = { - 'ROSTER': [self.handle_event_roster], - 'WARNING': [self.handle_event_warning], - 'ERROR': [self.handle_event_error], - 'INFORMATION': [self.handle_event_information], - 'ERROR_ANSWER': [self.handle_event_error_answer], - 'STATUS': [self.handle_event_status], - 'NOTIFY': [self.handle_event_notify], - 'MSGERROR': [self.handle_event_msgerror], - 'MSGSENT': [self.handle_event_msgsent], - 'MSGNOTSENT': [self.handle_event_msgnotsent], - 'SUBSCRIBED': [self.handle_event_subscribed], - 'UNSUBSCRIBED': [self.handle_event_unsubscribed], - 'SUBSCRIBE': [self.handle_event_subscribe], - 'AGENT_ERROR_INFO': [self.handle_event_agent_info_error], - 'AGENT_ERROR_ITEMS': [self.handle_event_agent_items_error], - 'AGENT_REMOVED': [self.handle_event_agent_removed], - 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], - 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], - 'AGENT_INFO_INFO': [self.handle_event_agent_info_info], - 'QUIT': [self.handle_event_quit], - 'NEW_ACC_CONNECTED': [self.handle_event_new_acc_connected], - 'NEW_ACC_NOT_CONNECTED': [self.handle_event_new_acc_not_connected], - 'ACC_OK': [self.handle_event_acc_ok], - 'ACC_NOT_OK': [self.handle_event_acc_not_ok], - 'MYVCARD': [self.handle_event_myvcard], - 'VCARD': [self.handle_event_vcard], - 'LAST_STATUS_TIME': [self.handle_event_last_status_time], - 'OS_INFO': [self.handle_event_os_info], - 'ENTITY_TIME': [self.handle_event_entity_time], - 'GC_NOTIFY': [self.handle_event_gc_notify], - 'GC_MSG': [self.handle_event_gc_msg], - 'GC_SUBJECT': [self.handle_event_gc_subject], - 'GC_CONFIG': [self.handle_event_gc_config], - 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], - 'GC_INVITATION': [self.handle_event_gc_invitation], - 'GC_AFFILIATION': [self.handle_event_gc_affiliation], - 'GC_PASSWORD_REQUIRED': [self.handle_event_gc_password_required], - 'BAD_PASSPHRASE': [self.handle_event_bad_passphrase], - 'ROSTER_INFO': [self.handle_event_roster_info], - 'BOOKMARKS': [self.handle_event_bookmarks], - 'CON_TYPE': [self.handle_event_con_type], - 'CONNECTION_LOST': [self.handle_event_connection_lost], - 'FILE_REQUEST': [self.handle_event_file_request], - 'GMAIL_NOTIFY': [self.handle_event_gmail_notify], - 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], - 'FILE_SEND_ERROR': [self.handle_event_file_send_error], - 'STANZA_ARRIVED': [self.handle_event_stanza_arrived], - 'STANZA_SENT': [self.handle_event_stanza_sent], - 'HTTP_AUTH': [self.handle_event_http_auth], - 'VCARD_PUBLISHED': [self.handle_event_vcard_published], - 'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published], - 'ASK_NEW_NICK': [self.handle_event_ask_new_nick], - 'SIGNED_IN': [self.handle_event_signed_in], - 'METACONTACTS': [self.handle_event_metacontacts], - 'ATOM_ENTRY': [self.handle_atom_entry], - 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], - 'PRIVACY_LISTS_RECEIVED': [self.handle_event_privacy_lists_received], - 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], - 'PRIVACY_LISTS_ACTIVE_DEFAULT': [self.handle_event_privacy_lists_active_default], - 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], - 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], - 'PING_SENT': [self.handle_event_ping_sent], - 'PING_REPLY': [self.handle_event_ping_reply], - 'PING_ERROR': [self.handle_event_ping_error], - 'SEARCH_FORM': [self.handle_event_search_form], - 'SEARCH_RESULT': [self.handle_event_search_result], - 'RESOURCE_CONFLICT': [self.handle_event_resource_conflict], - 'ROSTERX': [self.handle_event_roster_item_exchange], - 'PEP_CONFIG': [self.handle_event_pep_config], - 'UNIQUE_ROOM_ID_UNSUPPORTED': [self.handle_event_unique_room_id_unsupported], - 'UNIQUE_ROOM_ID_SUPPORTED': [self.handle_event_unique_room_id_supported], - 'GPG_PASSWORD_REQUIRED': [self.handle_event_gpg_password_required], - 'GPG_ALWAYS_TRUST': [self.handle_event_gpg_always_trust], - 'PASSWORD_REQUIRED': [self.handle_event_password_required], - 'SSL_ERROR': [self.handle_event_ssl_error], - 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], - 'PLAIN_CONNECTION': [self.handle_event_plain_connection], - 'INSECURE_SSL_CONNECTION': [self.handle_event_insecure_ssl_connection], - 'PUBSUB_NODE_REMOVED': [self.handle_event_pubsub_node_removed], - 'PUBSUB_NODE_NOT_REMOVED': [self.handle_event_pubsub_node_not_removed], - } - - def dispatch(self, event, account, data): - ''' - Dispatches an network event to the event handlers of this class. - - Return true if it could be dispatched to alteast one handler. - ''' - if event not in self.handlers: - log.warning('Unknown event %s dispatched to GUI: %s' % (event, data)) - return False - else: - log.debug('Event %s distpached to GUI: %s' % (event, data)) - for handler in self.handlers[event]: - handler(account, data) - return len(self.handlers[event]) - - -################################################################################ -### Methods dealing with gajim.events -################################################################################ - - def add_event(self, account, jid, type_, event_args): - '''add an event to the gajim.events var''' - # We add it to the gajim.events queue - # Do we have a queue? - jid = gajim.get_jid_without_resource(jid) - no_queue = len(gajim.events.get_events(account, jid)) == 0 - # type_ can be gc-invitation file-send-error file-error file-request-error - # file-request file-completed file-stopped - # event_type can be in advancedNotificationWindow.events_list - event_types = {'file-request': 'ft_request', - 'file-completed': 'ft_finished'} - event_type = event_types.get(type_) - show_in_roster = notify.get_show_in_roster(event_type, account, jid) - show_in_systray = notify.get_show_in_systray(event_type, account, jid) - event = gajim.events.create_event(type_, event_args, - show_in_roster=show_in_roster, - show_in_systray=show_in_systray) - gajim.events.add_event(account, jid, event) - - self.roster.show_title() - if no_queue: # We didn't have a queue: we change icons - if not gajim.contacts.get_contact_with_highest_priority(account, jid): - if type_ == 'gc-invitation': - self.roster.add_groupchat(jid, account, status='offline') - else: - # add contact to roster ("Not In The Roster") if he is not - self.roster.add_to_not_in_the_roster(account, jid) - else: - self.roster.draw_contact(jid, account) - - # Select the big brother contact in roster, it's visible because it has - # events. - family = gajim.contacts.get_metacontacts_family(account, jid) - if family: - nearby_family, bb_jid, bb_account = \ - self.roster._get_nearby_family_and_big_brother(family, account) - else: - bb_jid, bb_account = jid, account - self.roster.select_contact(bb_jid, bb_account) - - def handle_event(self, account, fjid, type_): - w = None - ctrl = None - session = None - - resource = gajim.get_resource_from_jid(fjid) - jid = gajim.get_jid_without_resource(fjid) - - if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): - w = self.msg_win_mgr.get_window(jid, account) - if jid in self.minimized_controls[account]: - self.roster.on_groupchat_maximized(None, jid, account) - return - else: - ctrl = self.msg_win_mgr.get_gc_control(jid, account) - - elif type_ in ('printed_chat', 'chat', ''): - # '' is for log in/out notifications - - if type_ != '': - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - event = gajim.events.get_first_event(account, jid, type_) - if not event: - return - - if type_ == 'printed_chat': - ctrl = event.parameters[0] - elif type_ == 'chat': - session = event.parameters[8] - ctrl = session.control - elif type_ == '': - ctrl = self.msg_win_mgr.get_control(fjid, account) - - if not ctrl: - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - # jid can have a window if this resource was lower when he sent - # message and is now higher because the other one is offline - if resource and highest_contact.resource == resource and \ - not self.msg_win_mgr.has_window(jid, account): - # remove resource of events too - gajim.events.change_jid(account, fjid, jid) - resource = None - fjid = jid - contact = None - if resource: - contact = gajim.contacts.get_contact(account, jid, resource) - if not contact: - contact = highest_contact - - ctrl = self.new_chat(contact, account, resource = resource, session = session) - - gajim.last_message_time[account][jid] = 0 # long time ago - - w = ctrl.parent_win - elif type_ in ('printed_pm', 'pm'): - # assume that the most recently updated control we have for this party - # is the one that this event was in - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - event = gajim.events.get_first_event(account, jid, type_) - - if type_ == 'printed_pm': - ctrl = event.parameters[0] - elif type_ == 'pm': - session = event.parameters[8] - - if session and session.control: - ctrl = session.control - elif not ctrl: - room_jid = jid - nick = resource - gc_contact = gajim.contacts.get_gc_contact(account, room_jid, - nick) - if gc_contact: - show = gc_contact.show - else: - show = 'offline' - gc_contact = gajim.contacts.create_gc_contact( - room_jid = room_jid, name = nick, show = show) - - if not session: - session = gajim.connections[account].make_new_session( - fjid, None, type_='pm') - - self.new_private_chat(gc_contact, account, session=session) - ctrl = session.control - - w = ctrl.parent_win - elif type_ in ('normal', 'file-request', 'file-request-error', - 'file-send-error', 'file-error', 'file-stopped', 'file-completed'): - # Get the first single message event - event = gajim.events.get_first_event(account, fjid, type_) - if not event: - # default to jid without resource - event = gajim.events.get_first_event(account, jid, type_) - if not event: - return - # Open the window - self.roster.open_event(account, jid, event) - else: - # Open the window - self.roster.open_event(account, fjid, event) - elif type_ == 'gmail': - url=gajim.connections[account].gmail_url - if url: - helpers.launch_browser_mailer('url', url) - elif type_ == 'gc-invitation': - event = gajim.events.get_first_event(account, jid, type_) - data = event.parameters - dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], - data[1], data[3]) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'subscription_request': - event = gajim.events.get_first_event(account, jid, type_) - data = event.parameters - dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - elif type_ == 'unsubscribed': - event = gajim.events.get_first_event(account, jid, type_) - contact = event.parameters - self.show_unsubscribed_dialog(account, contact) - gajim.events.remove_events(account, jid, event) - self.roster.draw_contact(jid, account) - if w: - w.set_active_tab(ctrl) - w.window.window.focus(gtk.get_current_event_time()) - # Using isinstance here because we want to catch all derived types - if isinstance(ctrl, ChatControlBase): - tv = ctrl.conv_textview - tv.scroll_to_end() - -################################################################################ -### Methods dealing with emoticons -################################################################################ - - def image_is_ok(self, image): - if not os.path.exists(image): - return False - img = gtk.Image() - try: - img.set_from_file(image) - except Exception: - return False - t = img.get_storage_type() - if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: - return False - return True - - @property - def basic_pattern_re(self): - try: - return self._basic_pattern_re - except AttributeError: - self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) - return self._basic_pattern_re - - @property - def emot_and_basic_re(self): - try: - return self._emot_and_basic_re - except AttributeError: - self._emot_and_basic_re = re.compile(self.emot_and_basic, - re.IGNORECASE + re.UNICODE) - return self._emot_and_basic_re - - @property - def sth_at_sth_dot_sth_re(self): - try: - return self._sth_at_sth_dot_sth_re - except AttributeError: - self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) - return self._sth_at_sth_dot_sth_re - - @property - def invalid_XML_chars_re(self): - try: - return self._invalid_XML_chars_re - except AttributeError: - self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) - return self._invalid_XML_chars_re - - def make_regexps(self): - # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) - # one escapes the metachars with \ - # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' - # \s matches any whitespace character - # \w any alphanumeric character - # \W any non-alphanumeric character - # \b means word boundary. This is a zero-width assertion that - # matches only at the beginning or end of a word. - # ^ matches at the beginning of lines - # - # * means 0 or more times - # + means 1 or more times - # ? means 0 or 1 time - # | means or - # [^*] anything but '*' (inside [] you don't have to escape metachars) - # [^\s*] anything but whitespaces and '*' - # (? in the matching string don't match ? or ) etc.. if at the end - # so http://be) will match http://be and http://be)be) will match http://be)be - - legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ - r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" - # NOTE: it's ok to catch www.gr such stuff exist! - - #FIXME: recognize xmpp: and treat it specially - links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ - r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ - r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" - - #2nd one: at_least_one_char@at_least_one_char.at_least_one_char - mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' - - #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) - #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* - formatting = r'|(?> sys.stderr, err_str - # it is good to notify the user - # in case he or she cannot see the output of the console - dialogs.ErrorDialog(_('Could not save your settings and preferences'), - err_str) - sys.exit() - - def save_avatar_files(self, jid, photo, puny_nick = None, local = False): - '''Saves an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image.''' - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) - if puny_nick: - path_to_file = os.path.join(path_to_file, puny_nick) - # remove old avatars - for typ in ('jpeg', 'png'): - if local: - path_to_original_file = path_to_file + '_local'+ '.' + typ - else: - path_to_original_file = path_to_file + '.' + typ - if os.path.isfile(path_to_original_file): - os.remove(path_to_original_file) - if local and photo: - pixbuf = photo - typ = 'png' - extension = '_local.png' # save local avatars as png file - else: - pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True) - if pixbuf is None: - return - extension = '.' + typ - if typ not in ('jpeg', 'png'): - gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ)) - typ = 'png' - extension = '.png' - path_to_original_file = path_to_file + extension - try: - pixbuf.save(path_to_original_file, typ) - except Exception, e: - log.error('Error writing avatar file %s: %s' % (path_to_original_file, - str(e))) - # Generate and save the resized, color avatar - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') - if pixbuf: - path_to_normal_file = path_to_file + '_notif_size_colored' + extension - try: - pixbuf.save(path_to_normal_file, 'png') - except Exception, e: - log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) - # Generate and save the resized, black and white avatar - bwbuf = gtkgui_helpers.get_scaled_pixbuf( - gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') - if bwbuf: - path_to_bw_file = path_to_file + '_notif_size_bw' + extension - try: - bwbuf.save(path_to_bw_file, 'png') - except Exception, e: - log.error('Error writing avatar file %s: %s' % \ - (path_to_original_file, str(e))) - - def remove_avatar_files(self, jid, puny_nick = None, local = False): - '''remove avatar files of a jid''' - puny_jid = helpers.sanitize_filename(jid) - path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) - if puny_nick: - path_to_file = os.path.join(path_to_file, puny_nick) - for ext in ('.jpeg', '.png'): - if local: - ext = '_local' + ext - path_to_original_file = path_to_file + ext - if os.path.isfile(path_to_file + ext): - os.remove(path_to_file + ext) - if os.path.isfile(path_to_file + '_notif_size_colored' + ext): - os.remove(path_to_file + '_notif_size_colored' + ext) - if os.path.isfile(path_to_file + '_notif_size_bw' + ext): - os.remove(path_to_file + '_notif_size_bw' + ext) - - def auto_join_bookmarks(self, account): - '''autojoin bookmarked GCs that have 'auto join' on for this account''' - for bm in gajim.connections[account].bookmarks: - if bm['autojoin'] in ('1', 'true'): - jid = bm['jid'] - # Only join non-opened groupchats. Opened one are already - # auto-joined on re-connection - if not jid in gajim.gc_connected[account]: - # we are not already connected - minimize = bm['minimize'] in ('1', 'true') - gajim.interface.join_gc_room(account, jid, bm['nick'], - bm['password'], minimize = minimize) - elif jid in self.minimized_controls[account]: - # more or less a hack: - # On disconnect the minimized gc contact instances - # were set to offline. Reconnect them to show up in the roster. - self.roster.add_groupchat(jid, account) - - def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, - nick): - '''add a bookmark for this account, sorted in bookmark list''' - bm = { - 'name': name, - 'jid': jid, - 'autojoin': autojoin, - 'minimize': minimize, - 'password': password, - 'nick': nick - } - place_found = False - index = 0 - # check for duplicate entry and respect alpha order - for bookmark in gajim.connections[account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) - return - if bookmark['name'] > bm['name']: - place_found = True - break - index += 1 - if place_found: - gajim.connections[account].bookmarks.insert(index, bm) - else: - gajim.connections[account].bookmarks.append(bm) - gajim.connections[account].store_bookmarks() - self.roster.set_actions_menu_needs_rebuild() - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) - - - # does JID exist only within a groupchat? - def is_pm_contact(self, fjid, account): - bare_jid = gajim.get_jid_without_resource(fjid) - - gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) - - if not gc_ctrl and \ - bare_jid in self.minimized_controls[account]: - gc_ctrl = self.minimized_controls[account][bare_jid] - - return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC - - def create_ipython_window(self): - try: - from ipython_view import IPythonView - except ImportError: - print 'ipython_view not found' - return - import pango - - if os.name == 'nt': - font = 'Lucida Console 9' - else: - font = 'Luxi Mono 10' - - window = gtk.Window() - window.set_size_request(750,550) - window.set_resizable(True) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) - view = IPythonView() - view.modify_font(pango.FontDescription(font)) - view.set_wrap_mode(gtk.WRAP_CHAR) - sw.add(view) - window.add(sw) - window.show_all() - def on_delete(win, event): - win.hide() - return True - window.connect('delete_event',on_delete) - view.updateNamespace({'gajim': gajim}) - gajim.ipython_window = window - - def run(self): - if self.systray_capabilities and gajim.config.get('trayicon') != 'never': - self.show_systray() - - self.roster = roster_window.RosterWindow() - for account in gajim.connections: - gajim.connections[account].load_roster_from_db() - - # get instances for windows/dialogs that will show_all()/hide() - self.instances['file_transfers'] = dialogs.FileTransfersWindow() - - gobject.timeout_add(100, self.autoconnect) - timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT - if in_seconds: - gobject.timeout_add_seconds(timeout, self.process_connections) - else: - gobject.timeout_add(timeout, self.process_connections) - gobject.timeout_add_seconds(gajim.config.get( - 'check_idle_every_foo_seconds'), self.read_sleepy) - - # when using libasyncns we need to process resolver in regular intervals - if resolver.USE_LIBASYNCNS: - gobject.timeout_add(200, gajim.resolver.process) - - # setup the indicator - if gajim.HAVE_INDICATOR: - notify.setup_indicator_server() - - def remote_init(): - if gajim.config.get('remote_control'): - try: - import remote_control - self.remote_ctrl = remote_control.Remote() - except Exception: - pass - gobject.timeout_add_seconds(5, remote_init) - - for account in gajim.connections: - if gajim.config.get_per('accounts', account, 'publish_tune') and \ - dbus_support.supported: - self.enable_music_listener() - break - - def __init__(self): - gajim.interface = self - gajim.thread_interface = ThreadInterface - # This is the manager and factory of message windows set by the module - self.msg_win_mgr = None - self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, - 'closed': {}} - self.emoticons_menu = None - # handler when an emoticon is clicked in emoticons_menu - self.emoticon_menuitem_clicked = None - self.minimized_controls = {} - self.status_sent_to_users = {} - self.status_sent_to_groups = {} - self.gpg_passphrase = {} - self.pass_dialog = {} - self.default_colors = { - 'inmsgcolor': gajim.config.get('inmsgcolor'), - 'outmsgcolor': gajim.config.get('outmsgcolor'), - 'statusmsgcolor': gajim.config.get('statusmsgcolor'), - 'urlmsgcolor': gajim.config.get('urlmsgcolor'), - } - - cfg_was_read = parser.read() - gajim.logger.reset_shown_unread_messages() - # override logging settings from config (don't take care of '-q' option) - if gajim.config.get('verbose'): - logging_helpers.set_verbose() - - # Is Gajim default app? - if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): - gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() - - for account in gajim.config.get_per('accounts'): - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - gajim.ZEROCONF_ACC_NAME = account - break - # Is gnome configured to activate row on single click ? - try: - import gconf - client = gconf.client_get_default() - click_policy = client.get_string( - '/apps/nautilus/preferences/click_policy') - if click_policy == 'single': - gajim.single_click = True - except Exception: - pass - # add default status messages if there is not in the config file - if len(gajim.config.get_per('statusmsg')) == 0: - default = gajim.config.statusmsg_default - for msg in default: - gajim.config.add_per('statusmsg', msg) - gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) - gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) - gajim.config.set_per('statusmsg', msg, 'subactivity', - default[msg][2]) - gajim.config.set_per('statusmsg', msg, 'activity_text', - default[msg][3]) - gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) - gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) - #add default themes if there is not in the config file - theme = gajim.config.get('roster_theme') - if not theme in gajim.config.get_per('themes'): - gajim.config.set('roster_theme', _('default')) - if len(gajim.config.get_per('themes')) == 0: - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', - 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', - 'contactfont', 'contactfontattrs', 'bannertextcolor', - 'bannerbgcolor'] - - default = gajim.config.themes_default - for theme_name in default: - gajim.config.add_per('themes', theme_name) - theme = default[theme_name] - for o in d: - gajim.config.set_per('themes', theme_name, o, - theme[d.index(o)]) - - if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: - gtkgui_helpers.autodetect_browser_mailer() - - gajim.idlequeue = idlequeue.get_idlequeue() - # resolve and keep current record of resolved hosts - gajim.resolver = resolver.get_resolver(gajim.idlequeue) - gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, - self.handle_event_file_rcv_completed, - self.handle_event_file_progress, - self.handle_event_file_error) - gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) - gajim.default_session_type = ChatControlSession - self.register_handlers() - if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ - and gajim.HAVE_ZEROCONF: - gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ - connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) - for account in gajim.config.get_per('accounts'): - if not gajim.config.get_per('accounts', account, 'is_zeroconf') and \ - gajim.config.get_per('accounts', account, 'active'): - gajim.connections[account] = common.connection.Connection(account) - - # gtk hooks - gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') - gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') - gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') - - self.instances = {} - - for a in gajim.connections: - self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, - 'search': {}, 'online_dialog': {}} - # online_dialog contains all dialogs that have a meaning only when we - # are not disconnected - self.minimized_controls[a] = {} - gajim.contacts.add_account(a) - gajim.groups[a] = {} - gajim.gc_connected[a] = {} - gajim.automatic_rooms[a] = {} - gajim.newly_added[a] = [] - gajim.to_be_removed[a] = [] - gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') - gajim.block_signed_in_notifications[a] = True - gajim.sleeper_state[a] = 0 - gajim.encrypted_chats[a] = [] - gajim.last_message_time[a] = {} - gajim.status_before_autoaway[a] = '' - gajim.transport_avatar[a] = {} - gajim.gajim_optional_features[a] = [] - gajim.caps_hash[a] = '' - - helpers.update_optional_features() - - self.remote_ctrl = None - - if gajim.config.get('networkmanager_support') and dbus_support.supported: - import network_manager_listener - - # Handle gnome screensaver - if dbus_support.supported: - def gnome_screensaver_ActiveChanged_cb(active): - if not active: - for account in gajim.connections: - if gajim.sleeper_state[account] == 'autoaway-forced': - # We came back online ofter gnome-screensaver autoaway - self.roster.send_status(account, 'online', - gajim.status_before_autoaway[account]) - gajim.status_before_autoaway[account] = '' - gajim.sleeper_state[account] = 'online' - return - if not gajim.config.get('autoaway'): - # Don't go auto away if user disabled the option - return - for account in gajim.connections: - if account not in gajim.sleeper_state or \ - not gajim.sleeper_state[account]: - continue - if gajim.sleeper_state[account] == 'online': - # we save out online status - gajim.status_before_autoaway[account] = \ - gajim.connections[account].status - # we go away (no auto status) [we pass True to auto param] - auto_message = gajim.config.get('autoaway_message') - if not auto_message: - auto_message = gajim.connections[account].status - else: - auto_message = auto_message.replace('$S','%(status)s') - auto_message = auto_message.replace('$T','%(time)s') - auto_message = auto_message % { - 'status': gajim.status_before_autoaway[account], - 'time': gajim.config.get('autoxatime') - } - self.roster.send_status(account, 'away', auto_message, - auto=True) - gajim.sleeper_state[account] = 'autoaway-forced' - - try: - bus = dbus.SessionBus() - bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, - 'ActiveChanged', 'org.gnome.ScreenSaver') - except Exception: - pass - - self.show_vcard_when_connect = [] - - self.sleeper = common.sleepy.Sleepy( - gajim.config.get('autoawaytime') * 60, # make minutes to seconds - gajim.config.get('autoxatime') * 60) - - gtkgui_helpers.make_jabber_state_images() - - self.systray_enabled = False - self.systray_capabilities = False - - if (os.name == 'nt'): - import statusicon - self.systray = statusicon.StatusIcon() - self.systray_capabilities = True - else: # use ours, not GTK+ one - # [FIXME: remove this when we migrate to 2.10 and we can do - # cool tooltips somehow and (not dying to keep) animation] - import systray - self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES - if self.systray_capabilities: - self.systray = systray.Systray() - - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png') - pix = gtk.gdk.pixbuf_new_from_file(path_to_file) - # set the icon to all windows - gtk.window_set_default_icon(pix) - - self.init_emoticons() - self.make_regexps() - - # get transports type from DB - gajim.transport_type = gajim.logger.get_transports_type() - - # test is dictionnary is present for speller - if gajim.config.get('use_speller'): - lang = gajim.config.get('speller_language') - if not lang: - lang = gajim.LANG - tv = gtk.TextView() - try: - import gtkspell - spell = gtkspell.Spell(tv, lang) - except (ImportError, TypeError, RuntimeError, OSError): - dialogs.AspellDictError(lang) - - if gajim.config.get('soundplayer') == '': - # only on first time Gajim starts - commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') - for command in commands: - if helpers.is_in_path(command): - if command == 'aplay': - command += ' -q' - gajim.config.set('soundplayer', command) - break - - self.last_ftwindow_update = 0 - - self.music_track_changed_signal = None - - -class ThreadInterface: - def __init__(self, func, func_args, callback, callback_args): - '''Call a function in a thread - - :param func: the function to call in the thread - :param func_args: list or arguments for this function - :param callback: callback to call once function is finished - :param callback_args: list of arguments for this callback - ''' - def thread_function(func, func_args, callback, callback_args): - output = func(*func_args) - gobject.idle_add(callback, output, *callback_args) - Thread(target=thread_function, args=(func, func_args, callback, - callback_args)).start() - - -class PassphraseRequest: - def __init__(self, keyid): - self.keyid = keyid - self.callbacks = [] - self.dialog_created = False - self.dialog = None - self.completed = False - - def interrupt(self): - self.dialog.window.destroy() - self.callbacks = [] - - def run_callback(self, account, callback): - gajim.connections[account].gpg_passphrase(self.passphrase) - callback() - - def add_callback(self, account, cb): - if self.completed: - self.run_callback(account, cb) - else: - self.callbacks.append((account, cb)) - if not self.dialog_created: - self.create_dialog(account) - - def complete(self, passphrase): - self.passphrase = passphrase - self.completed = True - if passphrase is not None: - gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, - self.keyid) - for (account, cb) in self.callbacks: - self.run_callback(account, cb) - del self.callbacks - - def create_dialog(self, account): - title = _('Passphrase Required') - second = _('Enter GPG key passphrase for key %(keyid)s (account ' - '%(account)s).') % {'keyid': self.keyid, 'account': account} - - def _cancel(): - # user cancelled, continue without GPG - self.complete(None) - - def _ok(passphrase, checked, count): - result = gajim.connections[account].test_gpg_passphrase(passphrase) - if result == 'ok': - # passphrase is good - self.complete(passphrase) - return - elif result == 'expired': - dialogs.ErrorDialog(_('GPG key expired'), - _('Your GPG key has expied, you will be connected to %s without ' - 'OpenPGP.') % account) - # Don't try to connect with GPG - gajim.connections[account].continue_connect_info[2] = False - self.complete(None) - return - - if count < 3: - # ask again - dialogs.PassphraseDialog(_('Wrong Passphrase'), - _('Please retype your GPG passphrase or press Cancel.'), - ok_handler=(_ok, count + 1), cancel_handler=_cancel) - else: - # user failed 3 times, continue without GPG - self.complete(None) - - self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), - cancel_handler=_cancel) - self.dialog_created = True \ No newline at end of file diff --git a/src/interface_test.py b/src/interface_test.py deleted file mode 100644 index 76751f096..000000000 --- a/src/interface_test.py +++ /dev/null @@ -1,88 +0,0 @@ -''' -Created on Oct 18, 2009 - -@author: stephan -''' -import unittest - -import common.configpaths -common.configpaths.gajimpaths.init(None) -common.configpaths.gajimpaths.init_profile("tmp-test-profile") - -from common import logging_helpers -logging_helpers.set_quiet() - - -from interface import Interface - -class Test(unittest.TestCase): - - - def test_instantiation(self): - ''' Test that we can proper initialize and do not fail on globals ''' - interface = Interface() - interface.run() - - def test_dispatch(self): - ''' Test dispatcher forwarding network events to handler_* methods ''' - sut = Interface() - - success = sut.dispatch('No Such Event', None, None) - self.assertFalse(success, msg="Unexisting event handled") - - success = sut.dispatch('STANZA_ARRIVED', None, None) - self.assertTrue(success, msg="Existing event must be handled") - - def test_register_unregister_single_handler(self): - ''' Register / Unregister a custom event handler ''' - sut = Interface() - event = 'TESTS_ARE_COOL_EVENT' - - self.called = False - def handler(account, data): - self.assertEqual(account, 'account') - self.assertEqual(data, 'data') - self.called = True - - self.assertFalse(self.called) - sut.register_handler('TESTS_ARE_COOL_EVENT', handler) - sut.dispatch(event, 'account', 'data') - self.assertTrue(self.called, msg="Handler should have been called") - - self.called = False - sut.unregister_handler('TESTS_ARE_COOL_EVENT', handler) - sut.dispatch(event, 'account', 'data') - self.assertFalse(self.called, msg="Handler should no longer be called") - - - def test_dispatch_to_multiple_handlers(self): - ''' Register and dispatch a single event to multiple handlers ''' - sut = Interface() - event = 'SINGLE_EVENT' - - self.called_a = False - self.called_b = False - - def handler_a(account, data): - self.assertFalse(self.called_a, msg="One must only be notified once") - self.called_a = True - - def handler_b(account, data): - self.assertFalse(self.called_b, msg="One must only be notified once") - self.called_b = True - - sut.register_handler(event, handler_a) - sut.register_handler(event, handler_b) - - # register again - sut.register_handler('SOME_OTHER_EVENT', handler_b) - sut.register_handler(event, handler_a) - - sut.dispatch(event, 'account', 'data') - self.assertTrue(self.called_a and self.called_b, - msg="Both handlers should have been called") - - -if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.test'] - unittest.main() \ No newline at end of file