# -*- coding:utf-8 -*- ## src/gajim.py ## ## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org> ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org> ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com> ## Norman Rasmussen <norman AT rasmussen.co.za> ## Stéphan Kochen <stephan AT kochen.nl> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com> ## Alex Mauer <hawke AT hawkesnest.net> ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com> ## Nikos Kouremenos <kourem AT gmail.com> ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com> ## Stefan Bethge <stefan AT lanpartei.de> ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net> ## James Newton <redshodan AT gmail.com> ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com> ## Julien Pivotto <roidelapluie AT gmail.com> ## Stephan Erb <steve-e AT h3c.de> ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org> ## ## 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 <http://www.gnu.org/licenses/>. ## import os import sys import re import time import math from subprocess import Popen from gi.repository import Gtk from gi.repository import GdkPixbuf from gi.repository import GLib from common import i18n from common import gajim from common import events from common import dbus_support if dbus_support.supported: from music_track_listener import MusicTrackListener from common import location_listener import dbus import gtkgui_helpers import gui_menu_builder import dialogs import notify import message_control from chat_control_base import ChatControlBase from chat_control import ChatControl from groupchat_control import GroupchatControl from groupchat_control import PrivateChatControl from message_window import MessageWindowMgr from atom_window import AtomWindow from session import ChatControlSession from common import sleepy from nbxmpp import idlequeue from nbxmpp import Hashes2 from common.zeroconf import connection_zeroconf from common import resolver from common import caps_cache from common import proxy65_manager from common import socks5 from common import helpers from common import passwords from common import logging_helpers from common.connection_handlers_events import OurShowEvent, \ FileRequestErrorEvent, FileTransferCompletedEvent from common.connection import Connection from common.file_props import FilesProp from common import pep import emoticons import roster_window import profile_window import config from threading import Thread from common import ged from common.configpaths import 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_db_error(self, unused, error): #('DB_ERROR', account, error) if self.db_error_dialog: return self.db_error_dialog = dialogs.ErrorDialog(_('Database Error'), error) def destroyed(win): self.db_error_dialog = None self.db_error_dialog.connect('destroy', destroyed) @staticmethod def handle_event_information(obj): if obj.popup: if obj.level == 'error': cls = dialogs.ErrorDialog elif obj.level == 'warn': cls = dialogs.WarningDialog elif obj.level == 'info': cls = dialogs.InformationDialog else: return cls(obj.pri_txt, GLib.markup_escape_text(obj.sec_txt)) def handle_ask_new_nick(self, account, room_jid, parent_win): title = _('Unable to join group chat') prompt = _('Your desired nickname in group chat\n' '<b>%s</b>\n' 'is in use or registered by another occupant.\n' 'Please 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, transient_for=parent_win) @staticmethod def handle_event_http_auth(obj): #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) def response(account, answer): obj.conn.build_http_auth_answer(obj.stanza, answer) def on_yes(is_checked, obj): response(obj, 'yes') account = obj.conn.name 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 obj.msg: sec_msg = obj.msg + '\n' + sec_msg dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' '%(url)s (ID: %(id)s)') % {'method': obj.method, 'url': obj.url, 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj), on_response_no=(response, obj, 'no')) def handle_event_iq_error(self, obj): #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode)) if str(obj.errcode) in ('400', '403', '406') and obj.id_: # show the error dialog ft = self.instances['file_transfers'] sid = obj.id_ if len(obj.id_) > 3 and obj.id_[2] == '_': sid = obj.id_[3:] file_props = FilesProp.getFileProp(obj.conn.name, sid) if file_props : if str(obj.errcode) == '400': file_props.error = -3 else: file_props.error = -4 gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=obj.conn, jid=obj.jid, file_props=file_props, error_msg=obj.errmsg)) obj.conn.disconnect_transfer(file_props) return elif str(obj.errcode) == '404': sid = obj.id_ if len(obj.id_) > 3 and obj.id_[2] == '_': sid = obj.id_[3:] file_props = FilesProp.getFileProp(obj.conn.name, sid) if file_props: self.handle_event_file_send_error(obj.conn.name, (obj.fjid, file_props)) obj.conn.disconnect_transfer(file_props) return ctrl = self.msg_win_mgr.get_control(obj.fjid, obj.conn.name) if ctrl and ctrl.type_id == message_control.TYPE_GC: ctrl.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg)) @staticmethod def handle_event_connection_lost(obj): # ('CONNECTION_LOST', account, [title, text]) path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48) account = obj.conn.name notify.popup(_('Connection Failed'), account, account, '', path, obj.title, obj.msg) @staticmethod def unblock_signed_in_notifications(account): gajim.block_signed_in_notifications[account] = False def handle_event_status(self, obj): # OUR status #('STATUS', account, show) account = obj.conn.name if obj.show in ('offline', 'error'): for name in list(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() if name in self.instances[account]['online_dialog']: # destroy handler may have already removed it del self.instances[account]['online_dialog'][name] for request in self.gpg_passphrase.values(): if request: request.interrupt(account=account) if account in self.pass_dialog: self.pass_dialog[account].window.destroy() if obj.show == 'offline': 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 GLib.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) if account in self.show_vcard_when_connect and obj.show not in ( 'offline', 'error'): self.edit_own_details(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.interface.roster.window) gajim.connections[account].request_vcard(jid) @staticmethod def handle_gc_error(gc_control, pritext, sectext): if gc_control and gc_control.autorejoin is not None: if gc_control.error_dialog: gc_control.error_dialog.destroy() def on_close(dummy): gc_control.error_dialog.destroy() gc_control.error_dialog = None gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext, on_response_ok=on_close, on_response_cancel=on_close) gc_control.error_dialog.set_modal(False) if gc_control.parent_win: gc_control.error_dialog.set_transient_for( gc_control.parent_win.window) else: d = dialogs.ErrorDialog(pritext, sectext) if gc_control and gc_control.parent_win: d.set_transient_for(gc_control.parent_win.window) d.set_modal(False) def handle_gc_password_required(self, account, room_jid, nick): def on_ok(text): gajim.connections[account].join_gc(nick, room_jid, text) gajim.gc_passwords[room_jid] = text gc_control.error_dialog = None 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) gc_control.error_dialog = None gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) if gc_control: if gc_control.error_dialog: gc_control.error_dialog.destroy() gc_control.error_dialog = 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) gc_control.error_dialog.input_entry.set_visibility(False) def handle_event_gc_presence(self, obj): gc_control = obj.gc_control parent_win = None if gc_control and gc_control.parent_win: parent_win = gc_control.parent_win.window if obj.ptype == 'error': if obj.errcode == '503': # maximum user number reached self.handle_gc_error(gc_control, _('Unable to join group chat'), _('<b>%s</b> is full')\ % obj.room_jid) elif (obj.errcode == '401') or (obj.errcon == 'not-authorized'): # password required to join self.handle_gc_password_required(obj.conn.name, obj.room_jid, obj.nick) elif (obj.errcode == '403') or (obj.errcon == 'forbidden'): # we are banned self.handle_gc_error(gc_control, _('Unable to join group chat'), _('You are banned from group chat <b>%s</b>.') % \ obj.room_jid) elif (obj.errcode == '404') or (obj.errcon in ('item-not-found', 'remote-server-not-found')): # group chat does not exist self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Group chat <b>%s</b> does not exist.') % obj.room_jid) elif (obj.errcode == '405') or (obj.errcon == 'not-allowed'): self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Group chat creation is not permitted.')) elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'): self.handle_gc_error(gc_control, _('Unable to join groupchat'), _('You must use your registered nickname in <b>%s</b>.')\ % obj.room_jid) elif (obj.errcode == '407') or (obj.errcon == \ 'registration-required'): self.handle_gc_error(gc_control, _('Unable to join group chat'), _('You are not in the members list in groupchat %s.') % \ obj.room_jid) elif (obj.errcode == '409') or (obj.errcon == 'conflict'): self.handle_ask_new_nick(obj.conn.name, obj.room_jid, parent_win) elif gc_control: gc_control.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg)) if gc_control and gc_control.autorejoin: gc_control.autorejoin = False @staticmethod def handle_event_gc_message(obj): if not obj.stanza.getTag('body'): # no <body> # It could be a voice request. See # http://www.xmpp.org/extensions/xep-0045.html#voiceapprove if obj.msg_obj.form_node: dialogs.SingleMessageWindow(obj.conn.name, obj.fjid, action='receive', from_whom=obj.fjid, subject='', message='', resource='', session=None, form_node=obj.msg_obj.form_node) def handle_event_presence(self, obj): # 'NOTIFY' (account, (jid, status, status message, resource, # priority, # keyID, timestamp, contact_nickname)) # # Contact changed show account = obj.conn.name jid = obj.jid show = obj.show status = obj.status resource = obj.resource or '' jid_list = gajim.contacts.get_jid_list(account) # unset custom status if (obj.old_show == 0 and obj.new_show > 1) or \ (obj.old_show > 1 and obj.new_show == 0 and obj.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] if gajim.jid_is_transport(jid): # It must be an agent # transport just signed in/out, don't show # popup notifications for 30s account_jid = account + '/' + jid gajim.block_signed_in_notifications[account_jid] = True GLib.timeout_add_seconds(30, self.unblock_signed_in_notifications, account_jid) highest = gajim.contacts.get_contact_with_highest_priority(account, jid) is_highest = (highest and highest.resource == resource) ctrl = self.msg_win_mgr.get_control(jid, account) if ctrl and ctrl.session and len(obj.contact_list) > 1: ctrl.remove_session(ctrl.session) def handle_event_msgerror(self, obj): #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session])) account = obj.conn.name jids = obj.fjid.split('/', 1) jid = jids[0] session = obj.session 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(obj.fjid, 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, account=account, name=nick, show=show) ctrl = self.new_private_chat(gc_c, account, session) ctrl.contact.our_chatstate = False ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { 'code': obj.error_code, 'msg': obj.error_msg}, 'status') return gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { 'code': obj.error_code, 'msg': obj.error_msg}, '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 = obj.error_msg if obj.msg: msg = _('error while sending %(message)s ( %(error)s )') % { 'message': obj.msg, 'error': msg} if session: session.roster_message(jid, msg, obj.time_, msg_type='error') @staticmethod def handle_event_msgsent(obj): #('MSGSENT', account, (jid, msg, keyID)) # do not play sound when standalone chatstate message (eg no msg) if obj.message and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): helpers.play_sound('message_sent') @staticmethod def handle_event_msgnotsent(obj): #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) msg = _('error while sending %(message)s ( %(error)s )') % { 'message': obj.message, 'error': obj.error} if not obj.session: # No session. This can happen when sending a message from # gajim-remote log.warning(msg) return obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name, msg_type='error') def handle_event_subscribe_presence(self, obj): #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 account = obj.conn.name if helpers.allow_popup_window(account) or not self.systray_enabled: if obj.jid in self.instances[account]['sub_request']: self.instances[account]['sub_request'][obj.jid].window.destroy() self.instances[account]['sub_request'][obj.jid] = \ dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account, obj.user_nick) return event = events.SubscriptionRequestEvent(obj.status, obj.user_nick) self.add_event(account, obj.jid, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48) event_type = _('Subscription request') notify.popup(event_type, obj.jid, account, 'subscription_request', path, event_type, obj.jid) def handle_event_subscribed_presence(self, obj): #('SUBSCRIBED', account, (jid, resource)) account = obj.conn.name if obj.jid in gajim.contacts.get_jid_list(account): c = gajim.contacts.get_first_contact_from_jid(account, obj.jid) c.resource = obj.resource 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 obj.jid in attached_keys: keyID = attached_keys[attached_keys.index(obj.jid) + 1] name = obj.jid.split('@', 1)[0] name = name.split('%', 1)[0] contact1 = gajim.contacts.create_contact(jid=obj.jid, account=account, name=name, groups=[], show='online', status='online', ask='to', resource=obj.resource, keyID=keyID) gajim.contacts.add_contact(account, contact1) self.roster.add_contact(obj.jid, account) dialogs.InformationDialog(_('Authorization accepted'), _('The contact "%s" has authorized you to see his or her status.') % obj.jid) 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 them as offline.\nDo you want to ' 'remove them 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_presence(self, obj): #('UNSUBSCRIBED', account, jid) account = obj.conn.name contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid) if not contact: return if helpers.allow_popup_window(account) or not self.systray_enabled: self.show_unsubscribed_dialog(account, contact) return event = events.UnsubscribedEvent(contact) self.add_event(account, obj.jid, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48) event_type = _('Unsubscribed') notify.popup(event_type, obj.jid, account, 'unsubscribed', path, event_type, obj.jid) @staticmethod def handle_event_register_agent_info(obj): # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) # info in a dataform if is_form is True if obj.is_form or 'instructions' in obj.config: config.ServiceRegistrationWindow(obj.agent, obj.config, obj.conn.name, obj.is_form) else: dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \ obj.agent, _('Check your connection or try again later.')) def handle_event_vcard(self, obj): # ('VCARD', account, data) '''vcard holds the vcard data''' our_jid = gajim.get_jid_from_account(obj.conn.name) if obj.jid == our_jid: if obj.nickname: gajim.nicks[obj.conn.name] = obj.nickname if obj.conn.name in self.show_vcard_when_connect: self.show_vcard_when_connect.remove(obj.conn.name) def handle_event_gc_config(self, obj): #('GC_CONFIG', account, (jid, form_node)) config is a dict account = obj.conn.name if obj.jid in gajim.automatic_rooms[account]: if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]: # We're converting chat to muc. allow participants to invite for f in obj.dataform.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 obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged()) user_list = {} for jid in gajim.automatic_rooms[account][obj.jid]['invities']: user_list[jid] = {'affiliation': 'member'} obj.conn.send_gc_affiliation_list(obj.jid, user_list) else: # use default configuration obj.conn.send_gc_config(obj.jid, obj.form_node) # invite contacts # check if it is necessary to add <continue /> continue_tag = False if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]: continue_tag = True if 'invities' in gajim.automatic_rooms[account][obj.jid]: for jid in gajim.automatic_rooms[account][obj.jid]['invities']: obj.conn.send_invite(obj.jid, jid, continue_tag=continue_tag) gc_control = self.msg_win_mgr.get_gc_control(obj.jid, account) if gc_control: gc_control.print_conversation( _('%(jid)s has been invited in this room') % { 'jid': jid}, graphics=False) del gajim.automatic_rooms[account][obj.jid] elif obj.jid not in self.instances[account]['gc_config']: self.instances[account]['gc_config'][obj.jid] = \ config.GroupchatConfigWindow(account, obj.jid, obj.dataform) def handle_event_gc_affiliation(self, obj): #('GC_AFFILIATION', account, (room_jid, users_dict)) account = obj.conn.name if obj.jid in self.instances[account]['gc_config']: self.instances[account]['gc_config'][obj.jid].\ affiliation_list_received(obj.users_dict) def handle_event_gc_decline(self, obj): account = obj.conn.name gc_control = self.msg_win_mgr.get_gc_control(obj.room_jid, account) if gc_control: if obj.reason: gc_control.print_conversation( _('%(jid)s declined the invitation: %(reason)s') % { 'jid': obj.jid_from, 'reason': obj.reason}, graphics=False) else: gc_control.print_conversation( _('%(jid)s declined the invitation') % { 'jid': obj.jid_from}, graphics=False) def handle_event_gc_invitation(self, obj): #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) account = obj.conn.name if helpers.allow_popup_window(account) or not self.systray_enabled: dialogs.InvitationReceivedDialog(account, obj.room_jid, obj.jid_from, obj.password, obj.reason, is_continued=obj.is_continued) return event = events.GcInvitationtEvent(obj.room_jid, obj.reason, obj.password, obj.is_continued, obj.jid_from) self.add_event(account, obj.jid_from, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48) event_type = _('Groupchat Invitation') notify.popup(event_type, obj.jid_from, account, 'gc-invitation', path, event_type, obj.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_gpg_passphrase(self, obj): #('BAD_PASSPHRASE', account, ()) if obj.use_gpg_agent: sectext = _('You configured Gajim to use OpenPGP agent, but there ' 'is no OpenPGP agent running or it returned a wrong passphrase.' '\n') sectext += _('You are currently connected without your OpenPGP ' 'key.') dialogs.WarningDialog(_('Wrong passphrase'), sectext) else: path = gtkgui_helpers.get_icon_path('gtk-dialog-warning', 48) account = obj.conn.name notify.popup('warning', account, account, '', path, _('Wrong OpenPGP passphrase'), _('You are currently connected without your OpenPGP key.')) self.forget_gpg_passphrase(obj.keyID) @staticmethod def handle_event_client_cert_passphrase(obj): def on_ok(passphrase, checked): obj.conn.on_client_cert_passphrase(passphrase, obj.con, obj.port, obj.secure_tuple) def on_cancel(): obj.conn.on_client_cert_passphrase('', obj.con, obj.port, obj.secure_tuple) dialogs.PassphraseDialog(_('Certificate Passphrase Required'), _('Enter the certificate passphrase for account %s') % \ obj.conn.name, ok_handler=on_ok, cancel_handler=on_cancel) def handle_event_gpg_password_required(self, obj): #('GPG_PASSWORD_REQUIRED', account, (callback,)) if obj.keyid in self.gpg_passphrase: request = self.gpg_passphrase[obj.keyid] else: request = PassphraseRequest(obj.keyid) self.gpg_passphrase[obj.keyid] = request request.add_callback(obj.conn.name, obj.callback) @staticmethod def handle_event_gpg_trust_key(obj): #('GPG_ALWAYS_TRUST', account, callback) def on_yes(checked): if checked: obj.conn.gpg.always_trust.append(obj.keyID) obj.callback(True) def on_no(): obj.callback(False) dialogs.YesNoDialog(_('Untrusted OpenPGP key'), _('The OpenPGP 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, obj): #('PASSWORD_REQUIRED', account, None) account = obj.conn.name if account in self.pass_dialog: return text = _('Enter your password for account %s') % account def on_ok(passphrase, save): if save: gajim.config.set_per('accounts', account, 'savepass', True) passwords.save_password(account, passphrase) obj.conn.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_oauth2_credentials(self, obj): account = obj.conn.name def on_ok(refresh): gajim.config.set_per('accounts', account, 'oauth2_refresh_token', refresh) st = gajim.config.get_per('accounts', account, 'last_status') msg = helpers.from_one_line(gajim.config.get_per('accounts', account, 'last_status_msg')) gajim.interface.roster.send_status(account, st, msg) del self.pass_dialog[account] def on_cancel(): gajim.config.set_per('accounts', account, 'oauth2_refresh_token', '') self.roster.set_state(account, 'offline') self.roster.update_status_combobox() del self.pass_dialog[account] instruction = _('Please copy / paste the refresh token from the website' ' that has just been opened.') self.pass_dialog[account] = dialogs.InputTextDialog( _('Oauth2 Credentials'), instruction, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) def handle_event_roster_info(self, obj): #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) account = obj.conn.name contacts = gajim.contacts.get_contacts(account, obj.jid) if (not obj.sub or obj.sub == 'none') and \ (not obj.ask or obj.ask == 'none') and not obj.nickname and \ not obj.groups: # contact removed us. if contacts: self.roster.remove_contact(obj.jid, account, backend=True) return elif not contacts: if obj.sub == 'remove': return # Add new contact to roster keyID = '' attached_keys = gajim.config.get_per('accounts', account, 'attached_gpg_keys').split() if obj.jid in attached_keys: keyID = attached_keys[attached_keys.index(obj.jid) + 1] contact = gajim.contacts.create_contact(jid=obj.jid, account=account, name=obj.nickname, groups=obj.groups, show='offline', sub=obj.sub, ask=obj.ask, keyID=keyID) gajim.contacts.add_contact(account, contact) self.roster.add_contact(obj.jid, account) else: # 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 obj.sub == 'remove': # another of our instance removed a contact. Remove it here too self.roster.remove_contact(obj.jid, account, backend=True) return update = False if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\ or old_groups != obj.groups: # c.get_shown_groups() has changed. Reflect that in # roster_window self.roster.remove_contact(obj.jid, account, force=True) update = True for contact in contacts: contact.name = obj.nickname or '' contact.sub = obj.sub contact.ask = obj.ask contact.groups = obj.groups or [] if update: self.roster.add_contact(obj.jid, account) # Refilter and update old groups for group in old_groups: self.roster.draw_group(group, account) self.roster.draw_contact(obj.jid, account) if obj.jid in self.instances[account]['sub_request'] and obj.sub in ( 'from', 'both'): self.instances[account]['sub_request'][obj.jid].window.destroy() def handle_event_bookmarks(self, obj): # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) # We received a bookmark item from the server (JEP48) # Auto join GC windows if neccessary gui_menu_builder.build_bookmark_menu(obj.conn.name) invisible_show = gajim.SHOW_LIST.index('invisible') # do not autojoin if we are invisible if obj.conn.connected == invisible_show: return GLib.idle_add(self.auto_join_bookmarks, obj.conn.name) 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, 'stop') if helpers.allow_popup_window(account): ft.show_send_error(file_props) return event = events.FileSendErrorEvent(file_props) self.add_event(account, jid, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, jid, account, 'file-send-error', path, event_type, file_props.name) @staticmethod def handle_event_gmail_notify(obj): jid = obj.jid gmail_new_messages = int(obj.newmsgs) gmail_messages_list = obj.gmail_messages_list if not gajim.config.get('notify_on_new_gmail_email'): return path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48) title = _('New e-mail on %(gmail_mail_address)s') % \ {'gmail_mail_address': jid} text = i18n.ngettext('You have %d new e-mail conversation', 'You have %d new e-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 command = gajim.config.get('notify_on_new_gmail_email_command') if command: Popen(command, shell=True) if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): helpers.play_sound('gmail_received') notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail', path_to_image=path, title=title, text=text) def handle_event_file_request_error(self, obj): # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) ft = self.instances['file_transfers'] ft.set_status(obj.file_props, 'stop') errno = obj.file_props.error if helpers.allow_popup_window(obj.conn.name): if errno in (-4, -5): ft.show_stopped(obj.jid, obj.file_props, obj.error_msg) else: ft.show_request_error(obj.file_props) return if errno in (-4, -5): event_class = events.FileErrorEvent msg_type = 'file-error' else: event_class = events.FileRequestErrorEvent msg_type = 'file-request-error' event = event_class(obj.file_props) self.add_event(obj.conn.name, obj.jid, event) if helpers.allow_showing_notification(obj.conn.name): # check if we should be notified path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, obj.jid, obj.conn.name, msg_type, path, title=event_type, text=obj.file_props.name) def handle_event_file_request(self, obj): account = obj.conn.name if obj.jid not in gajim.contacts.get_jid_list(account): keyID = '' attached_keys = gajim.config.get_per('accounts', account, 'attached_gpg_keys').split() if obj.jid in attached_keys: keyID = attached_keys[attached_keys.index(obj.jid) + 1] contact = gajim.contacts.create_not_in_roster_contact(jid=obj.jid, account=account, keyID=keyID) gajim.contacts.add_contact(account, contact) self.roster.add_contact(obj.jid, account) contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid) if obj.file_props.session_type == 'jingle': request = obj.stanza.getTag('jingle').getTag('content')\ .getTag('description').getTag('request') if request: # If we get a request instead ft_win = self.instances['file_transfers'] ft_win.add_transfer(account, contact, obj.file_props) return if helpers.allow_popup_window(account): self.instances['file_transfers'].show_file_request(account, contact, obj.file_props) return event = events.FileRequestEvent(obj.file_props) self.add_event(account, obj.jid, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48) txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( account, obj.jid) event_type = _('File Transfer Request') notify.popup(event_type, obj.jid, account, 'file-request', path_to_image=path, title=event_type, text=txt) @staticmethod def handle_event_file_error(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 __compare_hashes(self, account, file_props): session = gajim.connections[account].get_jingle_session(jid=None, sid=file_props.sid) ft_win = self.instances['file_transfers'] h = Hashes2() try: file_ = open(file_props.file_name, 'rb') except: return hash_ = h.calculateHash(file_props.algo, file_) file_.close() # If the hash we received and the hash of the file are the same, # then the file is not corrupt jid = file_props.sender if file_props.hash_ == hash_: GLib.idle_add(self.popup_ft_result, account, jid, file_props) GLib.idle_add(ft_win.set_status, file_props, 'ok') else: # wrong hash, we need to get the file again! file_props.error = -10 GLib.idle_add(self.popup_ft_result, account, jid, file_props) GLib.idle_add(ft_win.set_status, file_props, 'hash_error') # End jingle session if session: session.end_session() 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) gajim.nec.push_incoming_event(FileTransferCompletedEvent(None, file_props=file_props)) else: ft.set_status(file_props, 'stop') if file_props.stalled or file_props.paused: return if file_props.type_ == 'r': # we receive a file gajim.socks5queue.remove_receiver(file_props.sid, True, True) if file_props.session_type == 'jingle': if file_props.hash_ and file_props.error == 0: # We compare hashes in a new thread self.hashThread = Thread(target=self.__compare_hashes, args=(account, file_props)) self.hashThread.start() else: # We disn't get the hash, sender probably don't support that jid = file_props.sender self.popup_ft_result(account, jid, file_props) if file_props.error == 0: ft.set_status(file_props, 'ok') session = gajim.connections[account].get_jingle_session(jid=None, sid=file_props.sid) # End jingle session # TODO: only if there are no other parallel downloads in this session if session: session.end_session() else: # we send a file jid = file_props.receiver gajim.socks5queue.remove_sender(file_props.sid, True, True) self.popup_ft_result(account, jid, file_props) def popup_ft_result(self, account, jid, file_props): ft = self.instances['file_transfers'] 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')) elif file_props.error == -10: ft.show_hash_error(jid, file_props, account) elif file_props.error == -12: ft.show_stopped(jid, file_props, error_msg=_('SSL certificate error')) return msg_type = '' event_type = '' if file_props.error == 0 and gajim.config.get( 'notify_on_file_complete'): event_class = events.FileCompletedEvent msg_type = 'file-completed' event_type = _('File Transfer Completed') elif file_props.error in (-1, -6): event_class = events.FileStoppedEvent msg_type = 'file-stopped' event_type = _('File Transfer Stopped') elif file_props.error == -10: event_class = events.FileHashErrorEvent msg_type = 'file-hash-error' event_type = _('File Transfer Failed') 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: event = event_class(file_props) self.add_event(account, jid, event) if file_props is not None: if file_props.type_ == 'r': # get the name of the sender, as it is in the roster sender = 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 = _('%(filename)s received from %(name)s.')\ % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s from %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' else: # ft hash error txt = _('File transfer of %(filename)s from %(name)s ' 'failed.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' 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_name = 'gajim-ft_done' elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' else: # ft hash error txt = _('File transfer of %(filename)s to %(name)s ' 'failed.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' path = '' 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_signed_in(self, obj): """ SIGNED_IN event is emitted when we sign in, so handle it """ # ('SIGNED_IN', account, ()) # block signed in notifications for 30 seconds account = obj.conn.name gajim.block_signed_in_notifications[account] = True state = self.sleeper.getState() connected = obj.conn.connected if state != 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 == sleepy.STATE_AWAY and connected == 4) or \ (state == 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' if obj.conn.archiving_313_supported and gajim.config.get_per('accounts', account, 'sync_logs_with_server'): obj.conn.request_archive_on_signin() invisible_show = gajim.SHOW_LIST.index('invisible') # We cannot join rooms if we are invisible if connected == invisible_show: return # send currently played music if obj.conn.pep_supported and dbus_support.supported and \ gajim.config.get_per('accounts', account, 'publish_tune'): self.enable_music_listener() # enable location listener if obj.conn.pep_supported and dbus_support.supported and \ gajim.config.get_per('accounts', account, 'publish_location'): location_listener.enable() @staticmethod def handle_event_metacontacts(obj): gajim.contacts.define_metacontacts(obj.conn.name, obj.meta_list) @staticmethod def handle_atom_entry(obj): AtomWindow.newAtomEntry(obj.atom_entry) @staticmethod def handle_event_failed_decrypt(obj): details = _('Unable to decrypt message from %s\nIt may have been ' 'tampered with.') % obj.fjid dialogs.WarningDialog(_('Unable to decrypt message'), details) def handle_event_zc_name_conflict(self, obj): def on_ok(new_name): gajim.config.set_per('accounts', obj.conn.name, 'name', new_name) show = obj.conn.old_show status = obj.conn.status obj.conn.username = new_name obj.conn.change_status(show, status) def on_cancel(): obj.conn.change_status('offline', '') dlg = dialogs.InputDialog(_('Username Conflict'), _('Please type a new username for your local account'), input_str=obj.alt_name, is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel, transient_for=self.roster.window) def handle_event_resource_conflict(self, obj): # ('RESOURCE_CONFLICT', account, ()) # First we go offline, but we don't overwrite status message account = obj.conn.name conn = obj.conn self.roster.send_status(account, 'offline', conn.status) def on_ok(new_resource): gajim.config.set_per('accounts', account, 'resource', new_resource) self.roster.send_status(account, conn.old_show, conn.status) proposed_resource = conn.server_resource proposed_resource += gajim.config.get('gc_proposed_nick_char') dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), _('You are already connected to this account with the same ' 'resource. Please type a new one'), resource=proposed_resource, ok_handler=on_ok) def handle_event_jingleft_cancel(self, obj): ft = self.instances['file_transfers'] file_props = None # get the file_props of our session file_props = FilesProp.getFileProp(obj.conn.name, obj.sid) if not file_props: return ft.set_status(file_props, 'stop') file_props.error = -4 # is it the right error code? ft.show_stopped(obj.jid, file_props, 'Peer cancelled ' + 'the transfer') def handle_event_jingle_incoming(self, obj): # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, # data...)) # TODO: conditional blocking if peer is not in roster account = obj.conn.name content_types = obj.contents.media # check type of jingle session if 'audio' in content_types or 'video' in content_types: # a voip session... # we now handle only voip, so the only thing we will do here is # not to return from function pass else: # unknown session type... it should be declined in common/jingle.py return ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: if 'audio' in content_types: ctrl.set_audio_state('connection_received', obj.sid) if 'video' in content_types: ctrl.set_video_state('connection_received', obj.sid) dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid) if dlg: dlg.add_contents(content_types) return if helpers.allow_popup_window(account): dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid, content_types) return event = events.JingleIncomingEvent(obj.fjid, obj.sid, content_types) self.add_event(account, obj.jid, event) if helpers.allow_showing_notification(account): # TODO: we should use another pixmap ;-) txt = _('%s wants to start a voice chat.') % \ gajim.get_name_from_jid(account, obj.fjid) path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48) event_type = _('Voice Chat Request') notify.popup(event_type, obj.fjid, account, 'jingle-incoming', path_to_image=path, title=event_type, text=txt) def handle_event_jingle_connected(self, obj): # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) if obj.media in ('audio', 'video'): account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: if obj.media == 'audio': ctrl.set_audio_state('connected', obj.sid) else: ctrl.set_video_state('connected', obj.sid) def handle_event_jingle_disconnected(self, obj): # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: if obj.media is None: ctrl.stop_jingle(sid=obj.sid, reason=obj.reason) elif obj.media == 'audio': ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason) elif obj.media == 'video': ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason) dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid) if dialog: if obj.media is None: dialog.dialog.destroy() else: dialog.remove_contents((obj.media, )) def handle_event_jingle_error(self, obj): # ('JINGLE_ERROR', account, (peerjid, sid, reason)) account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl and obj.sid == ctrl.audio_sid: ctrl.set_audio_state('error', reason=obj.reason) @staticmethod def handle_event_roster_item_exchange(obj): # data = (action in [add, delete, modify], exchange_list, jid_from) dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action, obj.exchange_items_list, obj.fjid) def handle_event_ssl_error(self, obj): # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint, sha256_fingerprint)) account = obj.conn.name 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 obj.cert 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(obj.cert + '\n\n') f.close() gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', obj.fingerprint_sha1) gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha256', obj.fingerprint_sha256) if is_checked[1]: ignore_ssl_errors = gajim.config.get_per('accounts', account, 'ignore_ssl_errors').split() ignore_ssl_errors.append(str(obj.error_num)) gajim.config.set_per('accounts', account, 'ignore_ssl_errors', ' '.join(ignore_ssl_errors)) obj.conn.ssl_certificate_accepted() def on_cancel(): del self.instances[account]['online_dialog']['ssl_error'] obj.conn.disconnect(on_purpose=True) gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) pritext = _('Error verifying SSL certificate') sectext = _('There was an error verifying the SSL certificate of your ' 'XMPP server: %(error)s\nDo you still want to connect to this ' 'server?') % {'error': obj.error_text} if obj.error_num in (18, 27): checktext1 = _('Add this certificate to the list of trusted ' 'certificates.\nSHA-1 fingerprint of the certificate:\n%s' '\nSHA256 fingerprint of the certificate:\n%s') % \ (obj.fingerprint_sha1, obj.fingerprint_sha256) 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.SSLErrorDialog(obj.conn.name, obj.certificate, pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) self.instances[account]['online_dialog']['ssl_error'].set_title( _('SSL Certificate Verification for %s') % account) def handle_event_non_anonymous_server(self, obj): account = obj.conn.name server = gajim.config.get_per('accounts', account, 'hostname') dialogs.ErrorDialog(_('Non Anonymous Server'), sectext='Server "%s"' 'does not support anonymous connection' % server, transient_for=self.roster.window) def handle_event_fingerprint_error(self, obj): # ('FINGERPRINT_ERROR', account, (new_fingerprint_sha1,new_fingerprint_sha256,)) account = obj.conn.name def on_yes(is_checked): del self.instances[account]['online_dialog']['fingerprint_error'] gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', obj.new_fingerprint_sha1) gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha256', obj.new_fingerprint_sha256) # Reset the ignored ssl errors gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') obj.conn.ssl_certificate_accepted() def on_no(): del self.instances[account]['online_dialog']['fingerprint_error'] obj.conn.disconnect(on_purpose=True) gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) pritext = _('SSL certificate error') sectext = _('It seems the SSL certificate of account %(account)s has ' 'changed and is not valid or your connection is being compromised.\n\n' 'Old SHA-1 fingerprint: ' '%(old_sha1)s\nOld SHA-256 fingerprint: %(old_sha256)s\n\n' 'New SHA-1 fingerprint: %(new_sha1)s\nNew SHA-256 fingerprint: ' '%(new_sha256)s\n\nDo you still want to connect ' 'and update the fingerprint of the certificate?') % \ {'account': account, 'old_sha1': gajim.config.get_per('accounts', account, 'ssl_fingerprint_sha1'), 'old_sha256': gajim.config.get_per('accounts', account, 'ssl_fingerprint_sha256'), 'new_sha1': obj.new_fingerprint_sha1, 'new_sha256': obj.new_fingerprint_sha256} 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.CheckFingerprintDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no, account=obj.conn.name, certificate=obj.certificate) def handle_event_plain_connection(self, obj): # ('PLAIN_CONNECTION', account, (connection)) def on_ok(is_checked): if not is_checked[0]: if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'action_when_plaintext_connection', 'disconnect') on_cancel() return # On cancel call del self.instances, so don't call it another time # before del self.instances[obj.conn.name]['online_dialog']\ ['plain_connection'] if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'action_when_plaintext_connection', 'connect') obj.conn.connection_accepted(obj.xmpp_client, 'plain') def on_cancel(): del self.instances[obj.conn.name]['online_dialog']\ ['plain_connection'] obj.conn.disconnect(on_purpose=True) gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']: self.instances[obj.conn.name]['online_dialog']['plain_connection'].\ destroy() self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \ dialogs.PlainConnectionDialog(obj.conn.name, on_ok, on_cancel) def handle_event_insecure_ssl_connection(self, obj): # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) def on_ok(is_checked): if not is_checked[0]: on_cancel() return del self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'warn_when_insecure_ssl_connection', False) if obj.conn.connected == 0: # We have been disconnecting (too long time since window is # opened) # re-connect with auto-accept obj.conn.connection_auto_accepted = True show, msg = obj.conn.continue_connect_info[:2] self.roster.send_status(obj.conn.name, show, msg) return obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type) def on_cancel(): del self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] obj.conn.disconnect(on_purpose=True) gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='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[obj.conn.name]['online_dialog']: self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\ destroy() self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \ dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, is_modal=False) def handle_event_insecure_password(self, obj): # ('INSECURE_PASSWORD', account, ()) def on_ok(is_checked): if not is_checked[0]: on_cancel() return del self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'] if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'warn_when_insecure_password', False) if obj.conn.connected == 0: # We have been disconnecting (too long time since window is # opened) # re-connect with auto-accept obj.conn.connection_auto_accepted = True show, msg = obj.conn.continue_connect_info[:2] self.roster.send_status(obj.conn.name, show, msg) return obj.conn.accept_insecure_password() def on_cancel(): del self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'] obj.conn.disconnect(on_purpose=True) gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) pritext = _('Insecure connection') sectext = _('You are about to send your password unencrypted on an ' 'insecure connection. Are you sure you want to do that?') checktext1 = _('Yes, I really want to connect insecurely') checktext2 = _('_Do not ask me again') if 'insecure_password' in self.instances[obj.conn.name]\ ['online_dialog']: self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'].destroy() self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \ dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, is_modal=False) def create_core_handlers_list(self): self.handlers = { 'DB_ERROR': [self.handle_event_db_error], 'FILE_SEND_ERROR': [self.handle_event_file_send_error], 'atom-entry-received': [self.handle_atom_entry], 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase], 'bookmarks-received': [self.handle_event_bookmarks], 'client-cert-passphrase': [ self.handle_event_client_cert_passphrase], 'connection-lost': [self.handle_event_connection_lost], 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)], 'file-request-error': [self.handle_event_file_request_error], 'file-request-received': [self.handle_event_file_request], 'fingerprint-error': [self.handle_event_fingerprint_error], 'gc-invitation-received': [self.handle_event_gc_invitation], 'gc-decline-received': [self.handle_event_gc_decline], 'gc-presence-received': [self.handle_event_gc_presence], 'gc-message-received': [self.handle_event_gc_message], 'gmail-notify': [self.handle_event_gmail_notify], 'gpg-password-required': [self.handle_event_gpg_password_required], 'gpg-trust-key': [self.handle_event_gpg_trust_key], 'http-auth-received': [self.handle_event_http_auth], 'information': [self.handle_event_information], 'insecure-password': [self.handle_event_insecure_password], 'insecure-ssl-connection': \ [self.handle_event_insecure_ssl_connection], 'iq-error-received': [self.handle_event_iq_error], 'jingle-connected-received': [self.handle_event_jingle_connected], 'jingle-disconnected-received': [ self.handle_event_jingle_disconnected], 'jingle-error-received': [self.handle_event_jingle_error], 'jingle-request-received': [self.handle_event_jingle_incoming], 'jingleFT-cancelled-received': [self.handle_event_jingleft_cancel], 'message-error': [self.handle_event_msgerror], 'message-not-sent': [self.handle_event_msgnotsent], 'message-sent': [self.handle_event_msgsent], 'metacontacts-received': [self.handle_event_metacontacts], 'muc-admin-received': [self.handle_event_gc_affiliation], 'muc-owner-received': [self.handle_event_gc_config], 'oauth2-credentials-required': [self.handle_oauth2_credentials], 'our-show': [self.handle_event_status], 'password-required': [self.handle_event_password_required], 'plain-connection': [self.handle_event_plain_connection], 'presence-received': [self.handle_event_presence], 'register-agent-info-received': [self.handle_event_register_agent_info], 'roster-info': [self.handle_event_roster_info], 'roster-item-exchange-received': \ [self.handle_event_roster_item_exchange], 'signed-in': [self.handle_event_signed_in], 'ssl-error': [self.handle_event_ssl_error], 'non-anonymous-server-error': [self.handle_event_non_anonymous_server], 'stream-conflict-received': [self.handle_event_resource_conflict], 'subscribe-presence-received': [ self.handle_event_subscribe_presence], 'subscribed-presence-received': [ self.handle_event_subscribed_presence], 'unsubscribed-presence-received': [ self.handle_event_unsubscribed_presence], 'vcard-received': [self.handle_event_vcard], 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict], } def register_core_handlers(self): """ Register core handlers in Global Events Dispatcher (GED). This is part of rewriting whole events handling system to use GED. """ for event_name, event_handlers in self.handlers.items(): for event_handler in event_handlers: prio = ged.GUI1 if type(event_handler) == tuple: prio = event_handler[1] event_handler = event_handler[0] gajim.ged.register_event_handler(event_name, prio, event_handler) ################################################################################ ### Methods dealing with gajim.events ################################################################################ def add_event(self, account, jid, event): """ 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 # event can be in common.events.* # event_type can be in advancedNotificationWindow.events_list event_types = {'file-request': 'ft_request', 'file-completed': 'ft_finished'} event_type = event_types.get(event.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.show_in_roster = show_in_roster event.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 gajim.contacts.get_contact_with_highest_priority(account, jid): self.roster.draw_contact(jid, account) else: self.roster.add_to_not_in_the_roster(account, jid) # 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 = \ gajim.contacts.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.control elif type_ == 'chat': session = event.session 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 not event: return if type_ == 'printed_pm': ctrl = event.control elif type_ == 'pm': session = event.session 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, account=account, 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', 'file-hash-error', 'jingle-incoming'): # 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_) dialogs.InvitationReceivedDialog(account, event.room_jid, jid, event.password, event.reason, event.is_continued) 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_) dialogs.SubscriptionRequestWindow(jid, event.text, account, event.nick) 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_) self.show_unsubscribed_dialog(account, event.contact) gajim.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) if w: w.set_active_tab(ctrl) w.window.get_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_iter() ################################################################################ ### Methods dealing with emoticons ################################################################################ @property def basic_pattern_re(self): if not self._basic_pattern_re: self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) return self._basic_pattern_re @property def emot_and_basic_re(self): if not self._emot_and_basic_re: 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): if not self._sth_at_sth_dot_sth_re: 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): if not self._invalid_XML_chars_re: 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 '*' # (?<!\S) is a one char lookbehind assertion and asks for any leading # whitespace # and mathces beginning of lines so we have correct formatting detection # even if the the text is just '*foo*' # (?!\S) is the same thing but it's a lookahead assertion # \S*[^\s\W] --> 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\.\-_~:/\?#\[\]@!\$"\ r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\ r"|%[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"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[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'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\ r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\ r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)' basic_pattern = links + '|' + mail + '|' + legacy_prefixes link_pattern = basic_pattern self.link_pattern_re = re.compile(link_pattern, re.I | re.U) if gajim.config.get('ascii_formatting'): basic_pattern += formatting self.basic_pattern = basic_pattern emoticons_pattern = '' if gajim.config.get('emoticons_theme'): # When an emoticon is bordered by an alpha-numeric character it is # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc # We still allow multiple emoticons side-by-side like :P:P:P # sort keys by length so :qwe emot is checked before :q keys = sorted(emoticons.codepoints.keys(), key=len, reverse=True) emoticons_pattern_prematch = '' emoticons_pattern_postmatch = '' emoticon_length = 0 for emoticon in keys: # travel thru emoticons list emoticon_escaped = re.escape(emoticon) # espace regexp metachars # | means or in regexp emoticons_pattern += emoticon_escaped + '|' if (emoticon_length != len(emoticon)): # Build up expressions to match emoticons next to others emoticons_pattern_prematch = \ emoticons_pattern_prematch[:-1] + ')|(?<=' emoticons_pattern_postmatch = \ emoticons_pattern_postmatch[:-1] + ')|(?=' emoticon_length = len(emoticon) emoticons_pattern_prematch += emoticon_escaped + '|' emoticons_pattern_postmatch += emoticon_escaped + '|' # We match from our list of emoticons, but they must either have # whitespace, or another emoticon next to it to match successfully # [\w.] alphanumeric and dot (for not matching 8) in (2.8)) emoticons_pattern = '|' + r'(?:(?<![\w.]' + \ emoticons_pattern_prematch[:-1] + '))' + '(?:' + \ emoticons_pattern[:-1] + ')' + r'(?:(?![\w]' + \ emoticons_pattern_postmatch[:-1] + '))' # because emoticons match later (in the string) they need to be after # basic matches that may occur earlier self.emot_and_basic = basic_pattern + emoticons_pattern # needed for xhtml display self.emot_only = emoticons_pattern # at least one character in 3 parts (before @, after @, after .) self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]' # Invalid XML chars self.invalid_XML_chars = '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\ '[\ud800-\udfff]|[\ufffe-\uffff]' def init_emoticons(self): emot_theme = gajim.config.get('emoticons_theme') if not emot_theme: return transient_for = None if 'preferences' in gajim.interface.instances: transient_for = gajim.interface.instances['preferences'].window path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme) if not os.path.exists(path): # It's maybe a user theme path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme) if not os.path.exists(path): # theme doesn't exist, disable emoticons dialogs.WarningDialog(_('Emoticons disabled'), _('Your configured emoticons theme has not been found, so ' 'emoticons have been disabled.'), transient_for=transient_for) gajim.config.set('emoticons_theme', '') return if not emoticons.load(path): dialogs.WarningDialog( _('Emoticons disabled'), _('Your configured emoticons theme could not be loaded.' ' See the log for more details.'), transient_for=transient_for) gajim.config.set('emoticons_theme', '') return ################################################################################ ### Methods for opening new messages controls ################################################################################ def join_gc_room(self, account, room_jid, nick, password, minimize=False, is_continued=False): """ Join the room immediately """ if gajim.contacts.get_contact(account, room_jid) and \ not gajim.contacts.get_contact(account, room_jid).is_groupchat(): dialogs.ErrorDialog(_('This is not a group chat'), _('%s is already in your roster. Please check if %s is a ' 'correct group chat name. If it is, delete it from your roster ' 'and try joining the group chat again.') % (room_jid, room_jid)) return if not nick: nick = gajim.nicks[account] minimized_control = gajim.interface.minimized_controls[account].get( room_jid, None) if (self.msg_win_mgr.has_window(room_jid, account) or \ minimized_control) and gajim.gc_connected[account][room_jid]: if self.msg_win_mgr.has_window(room_jid, account): gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) win = gc_ctrl.parent_win win.set_active_tab(gc_ctrl) else: self.roster.on_groupchat_maximized(None, room_jid, account) dialogs.ErrorDialog(_('You are already in group chat %s') % \ room_jid) return invisible_show = gajim.SHOW_LIST.index('invisible') if gajim.connections[account].connected == invisible_show: dialogs.ErrorDialog( _('You cannot join a group chat while you are invisible')) return if minimized_control is None and not self.msg_win_mgr.has_window( room_jid, account): # Join new groupchat if minimize: # GCMIN contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) gc_control = GroupchatControl(None, contact, account) gajim.interface.minimized_controls[account][room_jid] = \ gc_control self.roster.add_groupchat(room_jid, account) else: self.new_room(room_jid, nick, account, is_continued=is_continued) elif minimized_control is None: # We are already in that groupchat gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) gc_control.nick = nick gc_control.parent_win.set_active_tab(gc_control) # Connect gajim.connections[account].join_gc(nick, room_jid, password) if password: gajim.gc_passwords[room_jid] = password def new_room(self, room_jid, nick, account, is_continued=False): # Get target window, create a control, and associate it with the window # GCMIN contact = gajim.contacts.create_contact(jid=room_jid, account=account, name=nick) mw = self.msg_win_mgr.get_window(contact.jid, account) if not mw: mw = self.msg_win_mgr.create_window(contact, account, GroupchatControl.TYPE_ID) gc_control = GroupchatControl(mw, contact, account, is_continued=is_continued) mw.new_tab(gc_control) mw.set_active_tab(gc_control) def new_private_chat(self, gc_contact, account, session=None): conn = gajim.connections[account] if not session and gc_contact.get_full_jid() in conn.sessions: sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\ values() if isinstance(s, ChatControlSession)] # look for an existing session with a chat control for s in sessions: if s.control: session = s break if not session and not len(sessions) == 0: # there are no sessions with chat controls, just take the first # one session = sessions[0] if not session: # couldn't find an existing ChatControlSession, just make a new one session = conn.make_new_session(gc_contact.get_full_jid(), None, 'pm') contact = gc_contact.as_contact() if not session.control: message_window = self.msg_win_mgr.get_window( gc_contact.get_full_jid(), account) if not message_window: message_window = self.msg_win_mgr.create_window(contact, account, message_control.TYPE_PM) session.control = PrivateChatControl(message_window, gc_contact, contact, account, session) message_window.new_tab(session.control) if gajim.events.get_events(account, gc_contact.get_full_jid()): # We call this here to avoid race conditions with widget validation session.control.read_queue() return session.control def new_chat(self, contact, account, resource=None, session=None): # Get target window, create a control, and associate it with the window type_ = message_control.TYPE_CHAT fjid = contact.jid if resource: fjid += '/' + resource mw = self.msg_win_mgr.get_window(fjid, account) if not mw: mw = self.msg_win_mgr.create_window(contact, account, type_, resource) chat_control = ChatControl(mw, contact, account, session, resource) mw.new_tab(chat_control) if len(gajim.events.get_events(account, fjid)): # We call this here to avoid race conditions with widget validation chat_control.read_queue() return chat_control def new_chat_from_jid(self, account, fjid, message=None): jid, resource = gajim.get_room_and_nick_from_fjid(fjid) contact = gajim.contacts.get_contact(account, jid, resource) added_to_roster = False if not contact: added_to_roster = True contact = self.roster.add_to_not_in_the_roster(account, jid, resource=resource) ctrl = self.msg_win_mgr.get_control(fjid, account) if not ctrl: ctrl = self.new_chat(contact, account, resource=resource) if len(gajim.events.get_events(account, fjid)): ctrl.read_queue() if message: buffer_ = ctrl.msg_textview.get_buffer() buffer_.set_text(message) mw = ctrl.parent_win mw.set_active_tab(ctrl) # For JEP-0172 if added_to_roster: ctrl.user_nick = gajim.nicks[account] GLib.idle_add(mw.window.grab_focus) return ctrl def on_open_chat_window(self, widget, contact, account, resource=None, session=None): # Get the window containing the chat fjid = contact.jid if resource: fjid += '/' + resource ctrl = None if session: ctrl = session.control if not ctrl: win = self.msg_win_mgr.get_window(fjid, account) if win: ctrl = win.get_control(fjid, account) if not ctrl: ctrl = self.new_chat(contact, account, resource=resource, session=session) # last message is long time ago gajim.last_message_time[account][ctrl.get_full_jid()] = 0 win = ctrl.parent_win win.set_active_tab(ctrl) if gajim.connections[account].is_zeroconf and \ gajim.connections[account].status in ('offline', 'invisible'): ctrl = win.get_control(fjid, account) if ctrl: ctrl.got_disconnected() ################################################################################ ### Other Methods ################################################################################ @staticmethod def change_awn_icon_status(status): if not dbus_support.supported: # do nothing if user doesn't have D-Bus bindings return try: bus = dbus.SessionBus() if not 'com.google.code.Awn' in bus.list_names(): # Awn is not installed return except Exception: return iconset = gajim.config.get('iconset') prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32') if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'): status = status + '.png' elif status == 'online': prefix = '' status = gtkgui_helpers.get_icon_path('org.gajim.Gajim', 32) path = os.path.join(prefix, status) try: obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn') awn = dbus.Interface(obj, 'com.google.code.Awn') awn.SetTaskIconByName('Gajim', os.path.abspath(path)) except Exception: pass def enable_music_listener(self): listener = MusicTrackListener.get() if not self.music_track_changed_signal: self.music_track_changed_signal = listener.connect( 'music-track-changed', self.music_track_changed) track = listener.get_playing_track() self.music_track_changed(listener, track) def disable_music_listener(self): listener = MusicTrackListener.get() listener.disconnect(self.music_track_changed_signal) self.music_track_changed_signal = None @staticmethod def music_track_changed(unused_listener, music_track_info, account=None): if not account: accounts = gajim.connections.keys() else: accounts = [account] is_paused = hasattr(music_track_info, 'paused') and \ music_track_info.paused == 0 if not music_track_info or is_paused: artist = title = source = '' else: artist = music_track_info.artist title = music_track_info.title source = music_track_info.album for acct in accounts: if not gajim.account_is_connected(acct): continue if not gajim.connections[acct].pep_supported: continue if not gajim.config.get_per('accounts', acct, 'publish_tune'): continue if gajim.connections[acct].music_track_info == music_track_info: continue gajim.connections[acct].send_tune(artist, title, source) gajim.connections[acct].music_track_info = music_track_info def read_sleepy(self): """ Check idle status and change that status if needed """ if not self.sleeper.poll(): # idle detection is not supported in that OS return False # stop looping in vain state = self.sleeper.getState() for account in gajim.connections: if account not in gajim.sleeper_state or \ not gajim.sleeper_state[account]: continue if state == sleepy.STATE_AWAKE: if gajim.sleeper_state[account] in ('autoaway', 'autoxa'): # we go online self.roster.send_status(account, 'online', gajim.status_before_autoaway[account]) gajim.status_before_autoaway[account] = '' gajim.sleeper_state[account] = 'online' if gajim.sleeper_state[account] == 'idle': # we go to the previous state connected = gajim.connections[account].connected self.roster.send_status(account, gajim.SHOW_LIST[connected], gajim.status_before_autoaway[account]) gajim.status_before_autoaway[account] = '' gajim.sleeper_state[account] = 'off' elif state == sleepy.STATE_AWAY and gajim.config.get('autoaway'): 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('autoawaytime') } self.roster.send_status(account, 'away', auto_message, auto=True) gajim.sleeper_state[account] = 'autoaway' elif gajim.sleeper_state[account] == 'off': # we save out online status gajim.status_before_autoaway[account] = \ gajim.connections[account].status connected = gajim.connections[account].connected self.roster.send_status(account, gajim.SHOW_LIST[connected], gajim.status_before_autoaway[account], auto=True) gajim.sleeper_state[account] = 'idle' elif state == sleepy.STATE_XA and \ gajim.sleeper_state[account] in ('online', 'autoaway', 'autoaway-forced') and gajim.config.get('autoxa'): # we go extended away [we pass True to auto param] auto_message = gajim.config.get('autoxa_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, 'xa', auto_message, auto=True) gajim.sleeper_state[account] = 'autoxa' return True # renew timeout (loop for ever) def autoconnect(self): """ Auto connect at startup """ # dict of account that want to connect sorted by status shows = {} for a in gajim.connections: if gajim.config.get_per('accounts', a, 'autoconnect'): if gajim.config.get_per('accounts', a, 'restore_last_status'): self.roster.send_status(a, gajim.config.get_per('accounts', a, 'last_status'), helpers.from_one_line( gajim.config.get_per('accounts', a, 'last_status_msg'))) continue show = gajim.config.get_per('accounts', a, 'autoconnect_as') if not show in gajim.SHOW_LIST: continue if not show in shows: shows[show] = [a] else: shows[show].append(a) def on_message(message, pep_dict): if message is None: return for a in shows[show]: self.roster.send_status(a, show, message) self.roster.send_pep(a, pep_dict) for show in shows: message = self.roster.get_status_message(show, on_message) return False def show_systray(self): self.systray_enabled = True self.systray.show_icon() def hide_systray(self): self.systray_enabled = False self.systray.hide_icon() @staticmethod def on_launch_browser_mailer(widget, url, kind): helpers.launch_browser_mailer(kind, url) def process_connections(self): """ Called each foo (200) miliseconds. Check for idlequeue timeouts """ try: gajim.idlequeue.process() except Exception: # Otherwise, an exception will stop our loop timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT if in_seconds: GLib.timeout_add_seconds(timeout, self.process_connections) else: GLib.timeout_add(timeout, self.process_connections) raise return True # renew timeout (loop for ever) @staticmethod def save_config(): err_str = parser.write() if err_str is not None: print(err_str, file=sys.stderr) # 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() @staticmethod def save_avatar_files(jid, photo, puny_nick = None, local = False): """ Save 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 if typ not in ('jpeg', 'png'): gajim.log.info('gtkpixbuf cannot save other than jpeg and '\ 'png formats. saving \'%s\' avatar as png file (originaly: %s)'\ % (jid, typ)) typ = 'png' extension = '.' + typ path_to_original_file = path_to_file + extension try: pixbuf.savev(path_to_original_file, typ, [], []) except Exception as 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.savev(path_to_normal_file, 'png', [], []) except Exception as 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.savev(path_to_bw_file, 'png', [], []) except Exception as e: log.error('Error writing avatar file %s: %s' % \ (path_to_original_file, str(e))) @staticmethod def remove_avatar_files(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') self.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() gui_menu_builder.build_bookmark_menu(account) 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 @staticmethod def get_pep_icon(pep_obj): if isinstance(pep_obj, pep.UserMoodPEP): received_mood = pep_obj._pep_specific_data['mood'] mood = received_mood if received_mood in pep.MOODS else 'unknown' return gtkgui_helpers.load_mood_icon(mood).get_pixbuf() elif isinstance(pep_obj, pep.UserTunePEP): path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png') return GdkPixbuf.Pixbuf.new_from_file(path) elif isinstance(pep_obj, pep.UserActivityPEP): pep_ = pep_obj._pep_specific_data activity = pep_['activity'] has_known_activity = activity in pep.ACTIVITIES has_known_subactivity = (has_known_activity and ('subactivity' in pep_) and (pep_['subactivity'] in pep.ACTIVITIES[activity])) if has_known_activity: if has_known_subactivity: subactivity = pep_['subactivity'] return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf() else: return gtkgui_helpers.load_activity_icon(activity).\ get_pixbuf() else: return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf() elif isinstance(pep_obj, pep.UserLocationPEP): icon = gtkgui_helpers.get_icon_pixmap('applications-internet', quiet=True) return icon @staticmethod def create_ipython_window(): try: from ipython_view import IPythonView except ImportError: print('ipython_view not found') return from gi.repository 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.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) view = IPythonView() view.override_font(Pango.FontDescription(font)) view.set_wrap_mode(Gtk.WrapMode.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, app): if gajim.config.get('trayicon') != 'never': self.show_systray() self.roster = roster_window.RosterWindow(app) if self.msg_win_mgr.mode == \ MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: self.msg_win_mgr.create_window(None, None, None) # Creating plugin manager import plugins gajim.plugin_manager = plugins.PluginManager() self.roster._before_fill() for account in gajim.connections: gajim.connections[account].load_roster_from_db() self.roster._after_fill() # get instances for windows/dialogs that will show_all()/hide() self.instances['file_transfers'] = dialogs.FileTransfersWindow() GLib.timeout_add(100, self.autoconnect) timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT if in_seconds: GLib.timeout_add_seconds(timeout, self.process_connections) else: GLib.timeout_add(timeout, self.process_connections) GLib.timeout_add_seconds(gajim.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) def remote_init(): if gajim.config.get('remote_control'): try: import remote_control self.remote_ctrl = remote_control.Remote() except Exception: pass GLib.timeout_add_seconds(5, remote_init) 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': {}, '24': {}, '32': {}, 'opened': {}, 'closed': {}} self.minimized_controls = {} self.status_sent_to_users = {} self.status_sent_to_groups = {} self.gpg_passphrase = {} self.pass_dialog = {} self.db_error_dialog = None self.default_colors = { 'inmsgcolor': gajim.config.get('inmsgcolor'), 'outmsgcolor': gajim.config.get('outmsgcolor'), 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'), 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'), 'statusmsgcolor': gajim.config.get('statusmsgcolor'), 'urlmsgcolor': gajim.config.get('urlmsgcolor'), 'markedmsgcolor': gajim.config.get('markedmsgcolor'), } self.handlers = {} self.roster = None self._invalid_XML_chars_re = None self._basic_pattern_re = None self._emot_and_basic_re = None self._sth_at_sth_dot_sth_re = None self.link_pattern_re = None self.invalid_XML_chars = None self.basic_pattern = None self.emot_and_basic = None self.sth_at_sth_dot_sth = None self.emot_only = None cfg_was_read = parser.read() if not cfg_was_read: # enable plugin_installer by default when creating config file gajim.config.set_per('plugins', 'plugin_installer', 'active', True) 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() 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: # gi.require_version('GConf', '2.0') # from gi.repository 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)]) # Add Tor proxy if there is not in the config if len(gajim.config.get_per('proxies')) == 0: default = gajim.config.proxies_default for proxy in default: gajim.config.add_per('proxies', proxy) gajim.config.set_per('proxies', proxy, 'type', default[proxy][0]) gajim.config.set_per('proxies', proxy, 'host', default[proxy][1]) gajim.config.set_per('proxies', proxy, 'port', default[proxy][2]) 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 # Creating Network Events Controller from common import nec gajim.nec = nec.NetworkEventsController() gajim.notification = notify.Notification() self.create_core_handlers_list() self.register_core_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] = 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': {}, 'sub_request': {}} # 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() # prepopulate data which we are sure of; note: we do not log these info for account in gajim.connections: gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])] gajimcaps.identities = [gajim.gajim_identity] gajimcaps.features = gajim.gajim_common_features + \ gajim.gajim_optional_features[account] self.remote_ctrl = None import network_watcher if dbus_support.supported: import upower_listener import logind_listener # Handle gnome screensaver if dbus_support.supported: def gnome_screensaver_ActiveChanged_cb(active): if not active: for account in gajim.connections: if gajim.account_is_connected(account) and \ 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': if not gajim.account_is_connected(account): continue # 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 = 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 import statusicon self.systray = statusicon.StatusIcon() pixs = [] for size in (16, 32, 48, 64, 128): pix = gtkgui_helpers.get_icon_pixmap('org.gajim.Gajim', size) if pix: pixs.append(pix) if pixs: # set the icon to all windows Gtk.Window.set_default_icon_list(pixs) 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, ValueError): dialogs.AspellDictError(lang) if gajim.config.get('soundplayer') == '': # only on first time Gajim starts commands = ('paplay', 'aplay', 'play', 'ossplay') for command in commands: if helpers.is_in_path(command): if command == 'paplay': command += ' -n gajim --property=media.role=event' if command in ('aplay', 'play'): command += ' -q' elif command == 'ossplay': command += ' -qq' gajim.config.set('soundplayer', command) break self.last_ftwindow_update = 0 self.music_track_changed_signal = None class PassphraseRequest: def __init__(self, keyid): self.keyid = keyid self.callbacks = [] self.dialog_created = False self.dialog = None self.passphrase = None self.completed = False def interrupt(self, account=None): if account: for (acct, cb) in self.callbacks: if acct == account: self.callbacks.remove((acct, cb)) else: self.callbacks = [] if not len(self.callbacks): self.dialog.window.destroy() 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: GLib.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, self.keyid) for (account, cb) in self.callbacks: self.run_callback(account, cb) self.callbacks = [] def create_dialog(self, account): title = _('Passphrase Required') second = _('Enter OpenPGP 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(_('OpenPGP key expired'), _('Your OpenPGP key has expired, 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 OpenPGP 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=None, callback_args=()): """ Call a function in a thread """ def thread_function(func, func_args, callback, callback_args): output = func(*func_args) if callback: GLib.idle_add(callback, output, *callback_args) Thread(target=thread_function, args=(func, func_args, callback, callback_args)).start()