Skip to content
Snippets Groups Projects
gajim.py 126 KiB
Newer Older
roidelapluie's avatar
roidelapluie committed
# -*- coding:utf-8 -*-
roidelapluie's avatar
roidelapluie committed
## src/gajim.py
Yann Leboulanger's avatar
Yann Leboulanger committed
##
roidelapluie's avatar
roidelapluie committed
## Copyright (C) 2003-2008 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>
roidelapluie's avatar
roidelapluie committed
##                    Stéphan Kochen <stephan AT kochen.nl>
roidelapluie's avatar
roidelapluie committed
## 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>
roidelapluie's avatar
roidelapluie committed
## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
roidelapluie's avatar
roidelapluie committed
##                    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>
Yann Leboulanger's avatar
Yann Leboulanger committed
##
Yann Leboulanger's avatar
Yann Leboulanger committed
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
Yann Leboulanger's avatar
Yann Leboulanger committed
## it under the terms of the GNU General Public License as published
Yann Leboulanger's avatar
Yann Leboulanger committed
## by the Free Software Foundation; version 3 only.
Yann Leboulanger's avatar
Yann Leboulanger committed
##
Yann Leboulanger's avatar
Yann Leboulanger committed
## Gajim is distributed in the hope that it will be useful,
Yann Leboulanger's avatar
Yann Leboulanger committed
## but WITHOUT ANY WARRANTY; without even the implied warranty of
roidelapluie's avatar
roidelapluie committed
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Yann Leboulanger's avatar
Yann Leboulanger committed
## GNU General Public License for more details.
##
Yann Leboulanger's avatar
Yann Leboulanger committed
## You should have received a copy of the GNU General Public License
roidelapluie's avatar
roidelapluie committed
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
##
import os
import warnings
Yann Leboulanger's avatar
Yann Leboulanger committed

if os.name == 'nt':
	warnings.filterwarnings(action='ignore')

	if os.path.isdir('gtk'):
		# Used to create windows installer with GTK included
		paths = os.environ['PATH']
		list_ = paths.split(';')
		new_list = []
		for p in list_:
			if p.find('gtk') < 0 and p.find('GTK') < 0:
				new_list.append(p)
		new_list.insert(0, 'gtk/lib')
		new_list.insert(0, 'gtk/bin')
		os.environ['PATH'] = ';'.join(new_list)
		os.environ['GTK_BASEPATH'] = 'gtk'
if os.name == 'nt':
	# needed for docutils
	sys.path.append('.')

from common import logging_helpers
logging_helpers.init('TERM' in os.environ)

# gajim.gui or gajim.gtk more appropriate ?
log = logging.getLogger('gajim.gajim')

import getopt
from common import i18n

	config_path = None
		shortargs = 'hqvl:p:c:'
		longargs = 'help quiet verbose loglevel= profile= config_path='
		opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
	except getopt.error, msg:
		print msg
		print 'for help use --help'
		sys.exit(2)
	for o, a in opts:
		if o in ('-h', '--help'):
			print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]'
			sys.exit()
		elif o in ('-q', '--quiet'):
			logging_helpers.set_quiet()
			logging_helpers.set_verbose()
		elif o in ('-p', '--profile'): # gajim --profile name
			profile = a
		elif o in ('-l', '--loglevel'):
			logging_helpers.set_loglevels(a)
		elif o in ('-c', '--config-path'):
			config_path = a
	return profile, config_path
profile, config_path = parseOpts()
del parseOpts
import locale
profile = unicode(profile, locale.getpreferredencoding())

import common.configpaths
common.configpaths.gajimpaths.init(config_path)
del config_path
common.configpaths.gajimpaths.init_profile(profile)
del profile

if os.name == 'nt':
	class MyStderr(object):
		_file = None
		_error = None
		def write(self, text):
			fname=os.path.join(common.configpaths.gajimpaths.root,
				os.path.split(sys.executable)[1]+'.log')
			if self._file is None and self._error is None:
				try:
					self._file = open(fname, 'a')
				except Exception, details:
					self._error = details
			if self._file is not None:
				self._file.write(text)
				self._file.flush()
		def flush(self):
			if self._file is not None:
				self._file.flush()

	sys.stderr = MyStderr()

Yann Leboulanger's avatar
Yann Leboulanger committed
# PyGTK2.10+ only throws a warning
warnings.filterwarnings('error', module='gtk')
try:
	import gtk
except Warning, msg:
	if str(msg) == 'could not open display':
		print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
		sys.exit()
Yann Leboulanger's avatar
Yann Leboulanger committed
warnings.resetwarnings()

if os.name == 'nt':
	warnings.filterwarnings(action='ignore')

	from common import gajim
except exceptions.DatabaseMalformed:
	pritext = _('Database Error')
	sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH
else:
	from common import dbus_support
	if dbus_support.supported:
		import dbus

	if os.name == 'posix': # dl module is Unix Only
		try: # rename the process name to gajim
			import dl
			libc = dl.open('/lib/libc.so.6')
			libc.call('prctl', 15, 'gajim\0', 0, 0, 0)
		except Exception:
	if gtk.pygtk_version < (2, 12, 0):
		pritext = _('Gajim needs PyGTK 2.12 or above')
		sectext = _('Gajim needs PyGTK 2.12 or above to run. Quiting...')
	elif gtk.gtk_version < (2, 12, 0):
		pritext = _('Gajim needs GTK 2.12 or above')
		sectext = _('Gajim needs GTK 2.12 or above to run. Quiting...')
	try:
		import gtk.glade # check if user has libglade (in pygtk and in gtk)
	except ImportError:
		pritext = _('GTK+ runtime is missing libglade support')
		if os.name == 'nt':
			sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net'
		else:
			sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
		from common import check_paths
	except exceptions.PysqliteNotAvailable, e:
		pritext = _('Gajim needs PySQLite2 to run')
		sectext = str(e)

	if os.name == 'nt':
		try:
			import winsound # windows-only built-in module for playing wav
			import win32api # do NOT remove. we req this module
		except Exception:
			pritext = _('Gajim needs pywin32 to run')
			sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018'
Yann Leboulanger's avatar
Yann Leboulanger committed
	dlg = gtk.MessageDialog(None,
		gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
		gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)

	dlg.format_secondary_text(sectext)
	dlg.run()
	dlg.destroy()
	sys.exit()

if not hasattr(gobject, 'timeout_add_seconds'):
	def timeout_add_seconds_fake(time_sec, *args):
		return gobject.timeout_add(time_sec * 1000, *args)
	gobject.timeout_add_seconds = timeout_add_seconds_fake
nkour's avatar
nkour committed

import gtkgui_helpers
import message_control

from chat_control import ChatControlBase
from chat_control import ChatControl
from groupchat_control import GroupchatControl
from groupchat_control import PrivateChatControl
from atom_window import AtomWindow
from session import ChatControlSession
import common.sleepy
nkour's avatar
nkour committed

from common.xmpp import idlequeue
from common.zeroconf import connection_zeroconf
from common import resolver
from common import proxy65_manager
from common import helpers
Yann Leboulanger's avatar
Yann Leboulanger committed
from common import dataforms
gajimpaths = common.configpaths.gajimpaths

pid_filename = gajimpaths['PID_FILE']
config_filename = gajimpaths['CONFIG_FILE']

import traceback
import errno

import dialogs
js's avatar
js committed
	except IOError:
		# probably file not found
		return False
	except Exception:
		traceback.print_exc()
		# PID file exists, but something happened trying to read PID
		# Could be 0.10 style empty PID file, so assume Gajim is running
		return True
	if os.name == 'nt':
		try:
			from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
		except Exception:
			return True

		class PROCESSENTRY32(Structure):
			_fields_ = [
				('dwSize', c_ulong, ),
				('cntUsage', c_ulong, ),
				('th32ProcessID', c_ulong, ),
				('th32DefaultHeapID', c_ulong, ),
				('th32ModuleID', c_ulong, ),
				('cntThreads', c_ulong, ),
				('th32ParentProcessID', c_ulong, ),
				('pcPriClassBase', c_ulong, ),
				('dwFlags', c_ulong, ),
				('szExeFile', c_char*512, ),
				]
			def __init__(self):
				Structure.__init__(self, 512+9*4)

		k = windll.kernel32
		k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
		k.CreateToolhelp32Snapshot.restype = c_int
		k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
		k.Process32First.restype = c_int
		k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
		k.Process32Next.restype = c_int

		def get_p(p):
			h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
			assert h > 0, 'CreateToolhelp32Snapshot failed'
			b = pointer(PROCESSENTRY32())
			f = k.Process32First(h, b)
			while f:
				if b.contents.th32ProcessID == p:
					return b.contents.szExeFile
				f = k.Process32Next(h, b)

		if get_p(pid) in ('python.exe', 'gajim.exe'):
		if not os.path.exists('/proc'):
			return True # no /proc, assume Gajim is running

Yann Leboulanger's avatar
Yann Leboulanger committed
			f = open('/proc/%d/cmdline'% pid)
		except IOError, e:
			if e.errno == errno.ENOENT:
				return False # file/pid does not exist
Yann Leboulanger's avatar
Yann Leboulanger committed
			raise

		n = f.read().lower()
		if n.find('gajim') < 0:
			return False
		return True # Running Gajim found at pid
	except Exception:
		traceback.print_exc()

	# If we are here, pidfile exists, but some unexpected error occured.
	# Assume Gajim is running.
	return True
	path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
	pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
	gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
	sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
	dialog = dialogs.YesNoDialog(pritext, sectext)
Yann Leboulanger's avatar
Yann Leboulanger committed
	dialog.popup()
Yann Leboulanger's avatar
Yann Leboulanger committed
	if dialog.run() != gtk.RESPONSE_YES:
		sys.exit(3)
Yann Leboulanger's avatar
Yann Leboulanger committed
	dialog.destroy()
jimpp's avatar
jimpp committed
	# run anyway, delete pid and useless global vars
	if os.path.exists(pid_filename):
		os.remove(pid_filename)
jimpp's avatar
jimpp committed
	del path_to_file
	del pix
	del pritext
	del sectext
	dialog.destroy()
# Create .gajim dir
pid_dir =  os.path.dirname(pid_filename)
if not os.path.exists(pid_dir):
	check_paths.create_path(pid_dir)
# Create pid file
try:
	f = open(pid_filename, 'w')
	f.write(str(os.getpid()))
	f.close()
except IOError, e:
	dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e))
	dlg.run()
	dlg.destroy()
	sys.exit()
jimpp's avatar
jimpp committed
del pid_dir

def on_exit():
	# delete pid file on normal exit
	if os.path.exists(pid_filename):
		os.remove(pid_filename)
	gajim.interface.roster.prepare_quit()
parser = optparser.OptionsParser(config_filename)
import roster_window
import profile_window
import config
class PassphraseRequest:
	def __init__(self, keyid):
		self.keyid = keyid
		self.callbacks = []
		self.dialog_created = False
	def interrupt(self):
		self.dialog.window.destroy()
		self.callbacks = []

	def run_callback(self, account, callback):
		gajim.connections[account].gpg_passphrase(self.passphrase)
		callback()

	def add_callback(self, account, cb):
		if self.completed:
			self.run_callback(account, cb)
		else:
			self.callbacks.append((account, cb))
			if not self.dialog_created:
				self.create_dialog(account)

	def complete(self, passphrase):
		self.passphrase = passphrase
		self.completed = True
		if passphrase is not None:
			gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase,
				self.keyid)
		for (account, cb) in self.callbacks:
			self.run_callback(account, cb)
		del self.callbacks

	def create_dialog(self, account):
		title = _('Passphrase Required')
		second = _('Enter GPG key passphrase for key %(keyid)s (account '
			'%(account)s).') % {'keyid': self.keyid, 'account': account}

		def _cancel():
			# user cancelled, continue without GPG
			result = gajim.connections[account].test_gpg_passphrase(passphrase)
			if result == 'ok':
			elif result == 'expired':
				dialogs.ErrorDialog(_('GPG key expired'),
					_('Your GPG key has expied, you will be connected to %s without '
					'OpenPGP.') % account)
				# Don't try to connect with GPG
				gajim.connections[account].continue_connect_info[2] = False
				self.complete(None)
				return
				# ask again
				dialogs.PassphraseDialog(_('Wrong Passphrase'),
					_('Please retype your GPG passphrase or press Cancel.'),
					ok_handler=(_ok, count + 1), cancel_handler=_cancel)
			else:
				# user failed 3 times, continue without GPG
				self.complete(None)
		self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1),
class ThreadInterface:
		def __init__(self, func, func_args, callback, callback_args):
			'''Call a function in a thread

			:param func: the function to call in the thread
			:param func_args: list or arguments for this function
			:param callback: callback to call once function is finished
			:param callback_args: list of arguments for this callback
			'''
			def thread_function(func, func_args, callback, callback_args):
				output = func(*func_args)
				gobject.idle_add(callback, output, *callback_args)
			Thread(target=thread_function, args=(func, func_args, callback,
################################################################################
### Methods handling events from connection
################################################################################
	def handle_event_roster(self, account, data):
		#('ROSTER', account, array)
		# FIXME: Those methods depend to highly on each other
		# and the order in which they are called
		self.roster.fill_contacts_and_groups_dicts(data, account)
		self.roster.add_account_contacts(account)
		self.roster.fire_up_unread_messages_events(account)
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Roster', (account, data))
	def handle_event_warning(self, unused, data):
		#('WARNING', account, (title_text, section_text))
		dialogs.WarningDialog(data[0], data[1])
	def handle_event_error(self, unused, data):
		#('ERROR', account, (title_text, section_text))

	def handle_event_information(self, unused, data):
		#('INFORMATION', account, (title_text, section_text))
		dialogs.InformationDialog(data[0], data[1])
	def handle_event_ask_new_nick(self, account, data):
		#('ASK_NEW_NICK', account, (room_jid,))
Brendan Taylor's avatar
Brendan Taylor committed
		gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
		if not gc_control and \
		room_jid in self.minimized_controls[account]:
			gc_control = self.minimized_controls[account][room_jid]
nicfit's avatar
nicfit committed
		if gc_control: # user may close the window before we are here
			title = _('Unable to join group chat')
			prompt = _('Your desired nickname in group chat %s is in use or '
				'registered by another occupant.\nPlease specify another nickname '
				'below:') % room_jid
			gc_control.show_change_nick_input_dialog(title, prompt)
	def handle_event_http_auth(self, account, data):
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
		#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
		def response(account, iq_obj, answer):
			self.dialog.destroy()
			gajim.connections[account].build_http_auth_answer(iq_obj, answer)

		def on_yes(is_checked, account, iq_obj):
			response(account, iq_obj, 'yes')

Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
		sec_msg = _('Do you accept this request?')
Yann Leboulanger's avatar
Yann Leboulanger committed
		if gajim.get_number_of_connected_accounts() > 1:
			sec_msg = _('Do you accept this request on account %s?') % account
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
		if data[4]:
			sec_msg = data[4] + '\n' + sec_msg
		self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
			'%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
			'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
			on_response_no=(response, account, data[3], 'no'))
Yann Leboulanger's avatar
Yann Leboulanger committed
	def handle_event_error_answer(self, account, array):
		#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
		id_, jid_from, errmsg, errcode = array
		if unicode(errcode) in ('400', '403', '406') and id_:
dkirov's avatar
dkirov committed
			# show the error dialog
			ft = self.instances['file_transfers']
			sid = id_
			if len(id_) > 3 and id_[2] == '_':
				sid = id_[3:]
			if sid in ft.files_props['s']:
dkirov's avatar
dkirov committed
				file_props = ft.files_props['s'][sid]
				if unicode(errcode) == '400':
					file_props['error'] = -3
				else:
					file_props['error'] = -4
Yann Leboulanger's avatar
Yann Leboulanger committed
				self.handle_event_file_request_error(account,
					(jid_from, file_props, errmsg))
dkirov's avatar
dkirov committed
				conn = gajim.connections[account]
				conn.disconnect_transfer(file_props)
				return
		elif unicode(errcode) == '404':
dkirov's avatar
dkirov committed
			conn = gajim.connections[account]
			sid = id_
			if len(id_) > 3 and id_[2] == '_':
				sid = id_[3:]
			if sid in conn.files_props:
dkirov's avatar
dkirov committed
				file_props = conn.files_props[sid]
Yann Leboulanger's avatar
Yann Leboulanger committed
				self.handle_event_file_send_error(account,
dkirov's avatar
dkirov committed
					(jid_from, file_props))
				conn.disconnect_transfer(file_props)
				return
Brendan Taylor's avatar
Brendan Taylor committed

		ctrl = self.msg_win_mgr.get_control(jid_from, account)
		if ctrl and ctrl.type_id == message_control.TYPE_GC:
			ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
	def handle_event_con_type(self, account, con_type):
		# ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
		self.roster.draw_account(account)
	def handle_event_connection_lost(self, account, array):
		# ('CONNECTION_LOST', account, [title, text])
		path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
			'connection_lost.png')
		path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
		notify.popup(_('Connection Failed'), account, account,
			'connection_failed', path, array[0], array[1])

	def unblock_signed_in_notifications(self, account):
		gajim.block_signed_in_notifications[account] = False
	def handle_event_status(self, account, status): # OUR status
		#('STATUS', account, status)
		model = self.roster.status_combobox.get_model()
		if status in ('offline', 'error'):
			for name in self.instances[account]['online_dialog'].keys():
				# .keys() is needed to not have a dictionary length changed during
				# iteration error
				self.instances[account]['online_dialog'][name].destroy()
				del self.instances[account]['online_dialog'][name]
			for request in self.gpg_passphrase.values():
				if request:
					request.interrupt()
		if status == 'offline':
nicfit's avatar
nicfit committed
			# sensitivity for this menuitem
			if gajim.get_number_of_connected_accounts() == 0:
				model[self.roster.status_message_menuitem_iter][3] = False
			gajim.block_signed_in_notifications[account] = True
			# 30 seconds after we change our status to sth else than offline
			# we stop blocking notifications of any kind
			# this prevents from getting the roster items as 'just signed in'
			# contacts. 30 seconds should be enough time
			gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account)
nicfit's avatar
nicfit committed
			# sensitivity for this menuitem
			model[self.roster.status_message_menuitem_iter][3] = True

		# Inform all controls for this account of the connection state change
		ctrls = self.msg_win_mgr.get_controls()
		if account in self.minimized_controls:
			# Can not be the case when we remove account
			ctrls += self.minimized_controls[account].values()
		for ctrl in ctrls:
nicfit's avatar
nicfit committed
			if ctrl.account == account:
				if status == 'offline' or (status == 'invisible' and \
				gajim.connections[account].is_zeroconf):
nicfit's avatar
nicfit committed
					ctrl.got_disconnected()
				else:
					# Other code rejoins all GCs, so we don't do it here
					if not ctrl.type_id == message_control.TYPE_GC:
						ctrl.got_connected()
				if ctrl.parent_win:
					ctrl.parent_win.redraw_tab(ctrl)
nicfit's avatar
nicfit committed

		self.roster.on_status_changed(account, status)
		if account in self.show_vcard_when_connect and status not in ('offline',
		'error'):
			self.edit_own_details(account)
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('AccountPresence', (status, account))
	def edit_own_details(self, account):
		jid = gajim.get_jid_from_account(account)
		if 'profile' not in self.instances[account]:
			self.instances[account]['profile'] = \
				profile_window.ProfileWindow(account)
			gajim.connections[account].request_vcard(jid)

	def handle_event_notify(self, account, array):
js's avatar
js committed
		# 'NOTIFY' (account, (jid, status, status message, resource,
		# priority, # keyID, timestamp, contact_nickname))
steve-e's avatar
steve-e committed
		#
		# Contact changed show

		# FIXME: Drop and rewrite...

		statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
			'invisible']
		# Ignore invalid show
		if array[1] not in statuss:
			return
		new_show = statuss.index(array[1])
Yann Leboulanger's avatar
Yann Leboulanger committed
		keyID = array[5]
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
		contact_nickname = array[7]
steve-e's avatar
steve-e committed
		# Get the proper keyID
		keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID)
steve-e's avatar
steve-e committed

		resource = array[3]
		if not resource:
			resource = ''
		priority = array[4]
		if gajim.jid_is_transport(jid):
			# It must be an agent
js's avatar
js committed
		highest = gajim.contacts. \
			get_contact_with_highest_priority(account, jid)
		was_highest = (highest and highest.resource == resource)

nkour's avatar
nkour committed
		# Update contact
		jid_list = gajim.contacts.get_jid_list(account)
		if ji in jid_list or jid == gajim.get_jid_from_account(account):
			lcontact = gajim.contacts.get_contacts(account, ji)
			resources = []
nkour's avatar
nkour committed
				resources.append(c.resource)
				if c.resource == resource:
					contact1 = c
			if contact1:
				if contact1.show in statuss:
					old_show = statuss.index(contact1.show)
steve-e's avatar
steve-e committed
				# nick changed
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
				if contact_nickname is not None and \
				contact1.contact_name != contact_nickname:
					contact1.contact_name = contact_nickname
					self.roster.draw_contact(jid, account)
steve-e's avatar
steve-e committed

				if old_show == new_show and contact1.status == status_message and \
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
				contact1.priority == priority: # no change
				contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
js's avatar
js committed
					# Presence of another resource of our
					# jid
					# Create self contact and add to roster
					if resource == conn.server_resource:
					# Ignore offline presence of unknown self resource
					if new_show < 2:
						return
					contact1 = gajim.contacts.create_contact(jid=ji,
						name=gajim.nicks[account], groups=['self_contact'],
						show=array[1], status=status_message, sub='both', ask='none',
						priority=priority, keyID=keyID, resource=resource,
						mood=conn.mood, tune=conn.tune, activity=conn.activity)
					gajim.contacts.add_contact(account, contact1)
					old_show = statuss.index(contact1.show)
js's avatar
js committed
				# FIXME: What am I?
				if (resources != [''] and (len(lcontact) != 1 or \
				lcontact[0].show != 'offline')) and jid.find('@') > 0:
					contact1 = gajim.contacts.copy_contact(contact1)
					lcontact.append(contact1)
				contact1.resource = resource
steve-e's avatar
steve-e committed
				self.roster.add_contact(contact1.jid, account)
			if contact1.jid.find('@') > 0 and len(lcontact) == 1:
				# It's not an agent
				if old_show == 0 and new_show > 1:
					if not contact1.jid in gajim.newly_added[account]:
						gajim.newly_added[account].append(contact1.jid)
					if contact1.jid in gajim.to_be_removed[account]:
						gajim.to_be_removed[account].remove(contact1.jid)
					gobject.timeout_add_seconds(5, self.roster.remove_newly_added,
				elif old_show > 1 and new_show == 0 and conn.connected > 1:
					if not contact1.jid in gajim.to_be_removed[account]:
						gajim.to_be_removed[account].append(contact1.jid)
					if contact1.jid in gajim.newly_added[account]:
						gajim.newly_added[account].remove(contact1.jid)
					self.roster.draw_contact(contact1.jid, account)
					gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed,
						contact1.jid, account)

			# unset custom status
			if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\
			and conn.connected > 1):
				if account in self.status_sent_to_users and \
				jid in self.status_sent_to_users[account]:
					del self.status_sent_to_users[account][jid]

			contact1.status = status_message
			contact1.priority = priority
			contact1.keyID = keyID
			timestamp = array[6]
			if timestamp:
				contact1.last_status_time = timestamp
			elif not gajim.block_signed_in_notifications[account]:
				# We're connected since more that 30 seconds
				contact1.last_status_time = time.localtime()
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
			contact1.contact_nickname = contact_nickname
steve-e's avatar
steve-e committed

		if gajim.jid_is_transport(jid):
			# It must be an agent
			if ji in jid_list:
				# Update existing iter and group counting
				self.roster.draw_contact(ji, account)
				self.roster.draw_group(_('Transports'), account)
				if new_show > 1 and ji in gajim.transport_avatar[account]:
js's avatar
js committed
					# transport just signed in.
					# request avatars
					for jid_ in gajim.transport_avatar[account][ji]:
						conn.request_vcard(jid_)
js's avatar
js committed
				# transport just signed in/out, don't show
				# popup notifications for 30s
				gajim.block_signed_in_notifications[account_ji] = True
js's avatar
js committed
				gobject.timeout_add_seconds(30,
					self.unblock_signed_in_notifications, account_ji)
			locations = (self.instances, self.instances[account])
			for location in locations:
				if 'add_contact' in location:
					if old_show == 0 and new_show > 1:
						location['add_contact'].transport_signed_in(jid)
						break
					elif old_show > 1 and new_show == 0:
						location['add_contact'].transport_signed_out(jid)
		elif ji in jid_list:
			# reset chatstate if needed:
			# (when contact signs out or has errors)
			if array[1] in ('offline', 'error'):
					contact1.composing_xep = None
js's avatar
js committed
				# TODO: This causes problems when another
				#	resource signs off!
				conn.remove_transfers_for_contact(contact1)

				# disable encryption, since if any messages are
				# lost they'll be not decryptable (note that
				# this contradicts XEP-0201 - trying to get that
				# in the XEP, though)

				# there won't be any sessions here if the contact terminated
				# their sessions before going offline (which we do)
				for sess in conn.get_sessions(ji):
					if (ji+'/'+resource) != str(sess.jid):
						continue
					if sess.control:
						sess.control.no_autonegotiation = False
					if sess.enable_encryption:
						sess.terminate_e2e()
						conn.delete_session(jid, sess.thread_id)
			self.roster.chg_contact_status(contact1, array[1], status_message,
				account)
			# Notifications
			if old_show < 2 and new_show > 1:
				notify.notify('contact_connected', jid, account, status_message)
					self.remote_ctrl.raise_signal('ContactPresence', (account,
						array))
				notify.notify('contact_disconnected', jid, account, status_message)
					self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
js's avatar
js committed
			# Status change (not connected/disconnected or
			# error (<1))
			elif new_show > 1:
				notify.notify('status_change', jid, account, [new_show,
					status_message])
					self.remote_ctrl.raise_signal('ContactStatus', (account, array))
jimpp's avatar
jimpp committed
		else:
js's avatar
js committed
			# FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't
			#	 follow the XEP, still the case in 2008.
			#	 It's maybe a GC_NOTIFY (specialy for MSN gc)
			self.handle_event_gc_notify(account, (jid, array[1], status_message,
				array[3], None, None, None, None, None, [], None, None))
js's avatar
js committed

		highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
		is_highest = (highest and highest.resource == resource)

		# disconnect the session from the ctrl if the highest resource has changed
		if (was_highest and not is_highest) or (not was_highest and is_highest):
			ctrl = self.msg_win_mgr.get_control(jid, account)
				ctrl.contact = highest
	def handle_event_msgerror(self, account, array):
		#'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session]))
		full_jid_with_resource = array[0]
		jids = full_jid_with_resource.split('/', 1)

		session = None
		if len(array) > 5:
			session = array[5]

Brendan Taylor's avatar
Brendan Taylor committed
		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]
				else:
					ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
					tv = gc_control.list_treeview
					model = tv.get_model()
					iter_ = gc_control.get_contact_iter(nick)
					if iter_:
						show = model[iter_][3]
					else:
						show = 'offline'
					gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
						name = nick, show = show)
					ctrl = self.new_private_chat(gc_c, account, session)

				ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
					'code': array[1], 'msg': array[2]}, 'status')
nicfit's avatar
nicfit committed

			gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
				'code': array[1], 'msg': array[2]}, 'status')
			if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid:
				gc_control.set_subject(gc_control.subject)
			return

		if gajim.jid_is_transport(jid):
			msg = _('error while sending %(message)s ( %(error)s )') % {
				'message': array[3], 'error': msg}
		if session:
			session.roster_message(jid, msg, array[4], msg_type='error')
	def handle_event_msgsent(self, account, array):
nkour's avatar
nkour committed
		#('MSGSENT', account, (jid, msg, keyID))
		msg = array[1]
		# do not play sound when standalone chatstate message (eg no msg)
		if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'):
			helpers.play_sound('message_sent')
		#('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
		msg = _('error while sending %(message)s ( %(error)s )') % {
			'message': array[2], 'error': array[1]}
		if not array[4]:
			# No session. This can happen when sending a message from gajim-remote
			log.warn(msg)
			return
		array[4].roster_message(array[0], msg, array[3], account,
	def handle_event_subscribe(self, account, array):
		#('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Subscribe', (account, array))
		jid = array[0]
		text = array[1]
		nick = array[2]
		if helpers.allow_popup_window(account) or not self.systray_enabled:
			dialogs.SubscriptionRequestWindow(jid, text, account, nick)
			return

		self.add_event(account, jid, 'subscription_request', (text, nick))

		if helpers.allow_showing_notification(account):
			path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
				'subscription_request.png')
			path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
			event_type = _('Subscription request')
			notify.popup(event_type, jid, account, 'subscription_request', path,
				event_type, jid)

	def handle_event_subscribed(self, account, array):
		#('SUBSCRIBED', account, (jid, resource))
		jid = array[0]
		if jid in gajim.contacts.get_jid_list(account):
			c = gajim.contacts.get_first_contact_from_jid(account, jid)
			c.resource = array[1]
			self.roster.remove_contact_from_groups(c.jid, account,
				[_('Not in Roster'), _('Observers')], update=False)
			keyID = ''
			attached_keys = gajim.config.get_per('accounts', account,
				'attached_gpg_keys').split()
			if jid in attached_keys:
				keyID = attached_keys[attached_keys.index(jid) + 1]
			name = jid.split('@', 1)[0]
			name = name.split('%', 1)[0]
			contact1 = gajim.contacts.create_contact(jid=jid, name=name,
				groups=[], show='online', status='online',
				ask='to', resource=array[1], keyID=keyID)
			gajim.contacts.add_contact(account, contact1)
			self.roster.add_contact(jid, account)
		dialogs.InformationDialog(_('Authorization accepted'),
nkour's avatar
nkour committed
				_('The contact "%s" has authorized you to see his or her status.')
nkour's avatar
nkour committed
		if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'):
			gajim.connections[account].ack_subscribed(jid)
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Subscribed', (account, array))
	def show_unsubscribed_dialog(self, account, contact):
		def on_yes(is_checked, list_):
			self.roster.on_req_usub(None, list_)
		list_ = [(contact, account)]
		dialogs.YesNoDialog(