gajim.py 72.4 KB
Newer Older
1 2
#!/bin/sh
''':'
3
exec python -OOt "$0" ${1+"$@"}
4 5
' '''
##	gajim.py
Yann Leboulanger's avatar
Yann Leboulanger committed
6
##
7
## Contributors for this file:
nkour's avatar
nkour committed
8
## - Yann Le Boulanger <asterix@lagaule.org>
nkour's avatar
nkour committed
9
## - Nikos Kouremenos <kourem@gmail.com>
dkirov's avatar
dkirov committed
10
## - Dimitur Kirov <dkirov@gmail.com>
11
## - Travis Shirk <travis@pobox.com>
Yann Leboulanger's avatar
Yann Leboulanger committed
12
##
13 14 15 16 17 18 19 20
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
##                         Vincent Hanquez <tab@snarc.org>
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
##                    Vincent Hanquez <tab@snarc.org>
##                    Nikos Kouremenos <nkour@jabber.org>
##                    Dimitur Kirov <dkirov@gmail.com>
##                    Travis Shirk <travis@pobox.com>
##                    Norman Rasmussen <norman@rasmussen.co.za>
Yann Leboulanger's avatar
Yann Leboulanger committed
21 22 23 24 25 26 27 28 29 30
##
## This program 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 2 only.
##
## This program 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.
##
31

nkour's avatar
nkour committed
32
import sys
nkour's avatar
nkour committed
33
import os
34
import urllib
35

sb's avatar
sb committed
36 37
from common import i18n

nicfit's avatar
nicfit committed
38 39
import message_control

nicfit's avatar
nicfit committed
40 41
from chat_control import ChatControlBase

42
from common import exceptions
sb's avatar
sb committed
43
from common.zeroconf import connection_zeroconf
44

sb's avatar
sb committed
45 46 47 48 49 50 51
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:
		pass
52

nkour's avatar
nkour committed
53 54 55 56
try:
	import gtk
except RuntimeError, msg:
	if str(msg) == 'could not open display':
nkour's avatar
nkour committed
57
		print >> sys.stderr, _('Gajim needs Xserver to run. Quiting...')
58
		sys.exit()
59
pritext = ''
nkour's avatar
nkour committed
60
if gtk.pygtk_version < (2, 6, 0):
nkour's avatar
nkour committed
61 62
	pritext = _('Gajim needs PyGTK 2.6 or above')
	sectext = _('Gajim needs PyGTK 2.6 or above to run. Quiting...')
nkour's avatar
nkour committed
63
elif gtk.gtk_version < (2, 6, 0):
nkour's avatar
nkour committed
64
	pritext = _('Gajim needs GTK 2.6 or above')
nkour's avatar
nkour committed
65
	sectext = _('Gajim needs GTK 2.6 or above to run. Quiting...')
nkour's avatar
nkour committed
66

67 68 69 70 71
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':
72
		sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net'
73
	else:
74
		sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
75 76 77 78

try:
	from common import check_paths
except exceptions.PysqliteNotAvailable, e:
nkour's avatar
nkour committed
79
	pritext = _('Gajim needs PySQLite2 to run')
80 81
	sectext = str(e)

sb's avatar
sb committed
82 83 84 85 86 87 88 89 90
if os.name == 'nt':
	try:
		import winsound # windows-only built-in module for playing wav
		import win32api
		import win32con
	except:
		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'

91
if pritext:
92 93 94 95 96 97 98 99 100
	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()

101
path = os.getcwd()
nkour's avatar
nkour committed
102
if '.svn' in os.listdir(path) or '_svn' in os.listdir(path):
103 104 105 106 107 108
	# import gtkexcepthook only for those that run svn
	# those than run with --verbose run from terminal so no need to care
	# about those
	import gtkexcepthook
del path

109
import gobject
110

Yann Leboulanger's avatar
bugfix  
Yann Leboulanger committed
111
import sre
112
import signal
113
import getopt
114
import time
115
import math
nkour's avatar
typo  
nkour committed
116

117
import gtkgui_helpers
118
import notify
119

120
import common.sleepy
nkour's avatar
nkour committed
121

dkirov's avatar
dkirov committed
122 123
from common.xmpp import idlequeue
from common import nslookup
124
from common import proxy65_manager
125
from common import socks5
126
from common import gajim
127
from common import helpers
128
from common import optparser
129

130 131
profile = ''
try:
132
	opts, args = getopt.getopt(sys.argv[1:], 'hvp:', ['help', 'verbose',
133
		'profile=', 'sm-config-prefix=', 'sm-client-id='])
134
except getopt.error, msg:
Yann Leboulanger's avatar
Yann Leboulanger committed
135 136 137
	print msg
	print 'for help use --help'
	sys.exit(2)
138
for o, a in opts:
Yann Leboulanger's avatar
Yann Leboulanger committed
139
	if o in ('-h', '--help'):
nkour's avatar
nkour committed
140
		print 'gajim [--help] [--verbose] [--profile name]'
141
		sys.exit()
Yann Leboulanger's avatar
Yann Leboulanger committed
142 143 144 145
	elif o in ('-v', '--verbose'):
		gajim.verbose = True
	elif o in ('-p', '--profile'): # gajim --profile name
		profile = a
146

sb's avatar
sb committed
147
pid_filename = os.path.expanduser('~/.gajim/gajim')
148 149 150 151 152
config_filename = os.path.expanduser('~/.gajim/config')
if os.name == 'nt':
	try:
		# Documents and Settings\[User Name]\Application Data\Gajim\logs
		config_filename = os.environ['appdata'] + '/Gajim/config'
sb's avatar
sb committed
153
		pid_filename = os.environ['appdata'] + '/Gajim/gajim'
154
	except KeyError:
nkour's avatar
nkour committed
155 156
		# win9x so ./config
		config_filename = 'config'
sb's avatar
sb committed
157
		pid_filename = 'gajim'
158

159
if profile:
Yann Leboulanger's avatar
Yann Leboulanger committed
160
	config_filename += '.%s' % profile
sb's avatar
sb committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
	pid_filename += '.%s' % profile

pid_filename += '.pid'
import dialogs
if os.path.exists(pid_filename):
	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
	pritext = _('Gajim is already running')
	sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
	dialog = dialogs.YesNoDialog(pritext, sectext)
	if dialog.get_response() != gtk.RESPONSE_YES:
		sys.exit(3)
	if os.path.exists(pid_filename):
		os.remove(pid_filename)
	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
f = open(pid_filename, 'a')
f.close()

def on_exit():
	# delete pid file on normal exit
	if os.path.exists(pid_filename):
		os.remove(pid_filename)

import atexit
atexit.register(on_exit)
193

194
parser = optparser.OptionsParser(config_filename)
195

196 197
import roster_window
import systray
sb's avatar
sb committed
198
import profile_window
199
import config
200

dkirov's avatar
dkirov committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
class GlibIdleQueue(idlequeue.IdleQueue):
	''' 
	Extends IdleQueue to use glib io_add_wath, instead of select/poll
	In another, `non gui' implementation of Gajim IdleQueue can be used safetly.
	'''
	def init_idle(self):
		''' this method is called at the end of class constructor.
		Creates a dict, which maps file/pipe/sock descriptor to glib event id'''
		self.events = {}
		if gtk.pygtk_version >= (2, 8, 0):
			# time() is already called in glib, we just get the last value 
			# overrides IdleQueue.current_time()
			self.current_time = lambda: gobject.get_current_time()
			
	def add_idle(self, fd, flags):
		''' this method is called when we plug a new idle object.
		Start listening for events from fd
		'''
		res = gobject.io_add_watch(fd, flags, self.process_events, 
										priority=gobject.PRIORITY_LOW)
		# store the id of the watch, so that we can remove it on unplug
		self.events[fd] = res
	
	def remove_idle(self, fd):
		''' this method is called when we unplug a new idle object.
		Stop listening for events from fd
		'''
		gobject.source_remove(self.events[fd])
		del(self.events[fd])
	
	def process(self):
		self.check_time_events()
	
234
class Interface:
235
	def handle_event_roster(self, account, data):
236
		#('ROSTER', account, array)
237
		self.roster.fill_contacts_and_groups_dicts(data, account)
238
		self.roster.add_account_contacts(account)
239
		self.roster.fire_up_unread_messages_events(account)
240 241
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Roster', (account, data))
242

243 244
	def handle_event_warning(self, unused, data):
		#('WARNING', account, (title_text, section_text))
245
		dialogs.WarningDialog(data[0], data[1])
246

247 248
	def handle_event_error(self, unused, data):
		#('ERROR', account, (title_text, section_text))
249
		dialogs.ErrorDialog(data[0], data[1])
250 251 252

	def handle_event_information(self, unused, data):
		#('INFORMATION', account, (title_text, section_text))
253
		dialogs.InformationDialog(data[0], data[1])
254
		
255 256
	def handle_event_ask_new_nick(self, account, data):
		#('ASK_NEW_NICK', account, (room_jid, title_text, prompt_text, proposed_nick))
257 258 259
		room_jid = data[0]
		title = data[1]
		prompt = data[2]
260
		proposed_nick = data[3]
261
		gc_control = self.msg_win_mgr.get_control(room_jid, account)
nicfit's avatar
nicfit committed
262 263
		if gc_control: # user may close the window before we are here
			gc_control.show_change_nick_input_dialog(title, prompt, proposed_nick)
264

265
	def handle_event_http_auth(self, account, data):
266
		#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj))
267 268 269 270 271 272 273 274
		def response(widget, account, iq_obj, answer):
			self.dialog.destroy()
			gajim.connections[account].build_http_auth_answer(iq_obj, answer)

		self.dialog = dialogs.YesNoDialog(_('HTTP (%s) Authorization for %s (id: %s)') \
			% (data[0], data[1], data[2]), _('Do you accept this request?'),
			on_response_yes = (response, account, data[3], 'yes'),
			on_response_no = (response, account, data[3], 'no'))
275

Yann Leboulanger's avatar
Yann Leboulanger committed
276
	def handle_event_error_answer(self, account, array):
sb's avatar
sb committed
277
		#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
278
		id, jid_from, errmsg, errcode = array
279
		if unicode(errcode) in ('403', '406') and id:
dkirov's avatar
dkirov committed
280
			# show the error dialog
281
			ft = self.instances['file_transfers']
dkirov's avatar
dkirov committed
282 283 284 285 286 287 288
			sid = id
			if len(id) > 3 and id[2] == '_':
				sid = id[3:]
			if ft.files_props['s'].has_key(sid):
				file_props = ft.files_props['s'][sid]
				file_props['error'] = -4
				self.handle_event_file_request_error(account, 
sb's avatar
sb committed
289
					(jid_from, file_props, errmsg))
dkirov's avatar
dkirov committed
290 291 292
				conn = gajim.connections[account]
				conn.disconnect_transfer(file_props)
				return
293
		elif unicode(errcode) == '404':
dkirov's avatar
dkirov committed
294
			conn = gajim.connections[account]
dkirov's avatar
dkirov committed
295 296 297 298 299 300 301 302 303
			sid = id
			if len(id) > 3 and id[2] == '_':
				sid = id[3:]
			if conn.files_props.has_key(sid):
				file_props = conn.files_props[sid]
				self.handle_event_file_send_error(account, 
					(jid_from, file_props))
				conn.disconnect_transfer(file_props)
				return
304
		ctrl = self.msg_win_mgr.get_control(jid_from, account)
nkour's avatar
nkour committed
305 306
		if ctrl and ctrl.type_id == message_control.TYPE_GC:
			ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
Yann Leboulanger's avatar
Yann Leboulanger committed
307

308 309
	def handle_event_con_type(self, account, con_type):
		# ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp'
310
		gajim.con_types[account] = con_type
311
		self.roster.draw_account(account)
312

sb's avatar
sb committed
313 314 315 316 317 318 319 320
	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])

321 322
	def unblock_signed_in_notifications(self, account):
		gajim.block_signed_in_notifications[account] = False
323

324
	def handle_event_status(self, account, status): # OUR status
325
		#('STATUS', account, status)
326 327
		model = self.roster.status_combobox.get_model()
		if status == 'offline':
nicfit's avatar
nicfit committed
328
			# sensitivity for this menuitem
329 330
			if gajim.get_number_of_connected_accounts() == 0:
				model[self.roster.status_message_menuitem_iter][3] = False
331
			gajim.block_signed_in_notifications[account] = True
332
		else:
333 334 335 336 337
			# 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(30000, self.unblock_signed_in_notifications, account)
nicfit's avatar
nicfit committed
338 339 340 341
			# sensitivity for this menuitem
			model[self.roster.status_message_menuitem_iter][3] = True

		# Inform all controls for this account of the connection state change
342
		for ctrl in self.msg_win_mgr.get_controls():
nicfit's avatar
nicfit committed
343 344 345 346 347 348 349 350 351
			if ctrl.account == account:
				if status == 'offline':
					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()
				ctrl.parent_win.redraw_tab(ctrl)

352
		self.roster.on_status_changed(account, status)
353
		if account in self.show_vcard_when_connect:
354
			self.edit_own_details(account)
355 356
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('AccountPresence', (status, account))
357
	
358 359
	def edit_own_details(self, account):
		jid = gajim.get_jid_from_account(account)
sb's avatar
sb committed
360 361 362
		if not self.instances[account].has_key('profile'):
			self.instances[account]['profile'] = \
				profile_window.ProfileWindow(account)
363 364
			gajim.connections[account].request_vcard(jid)

365
	def handle_event_notify(self, account, array):
366
		# 'NOTIFY' (account, (jid, status, status message, resource, priority,
367
		# keyID, timestamp))
nkour's avatar
nkour committed
368 369 370
		# if we're here it means contact changed show
		statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
			'invisible']
371
		old_show = 0
Yann Leboulanger's avatar
Yann Leboulanger committed
372
		new_show = statuss.index(array[1])
373
		status_message = array[2]
374
		jid = array[0].split('/')[0]
Yann Leboulanger's avatar
Yann Leboulanger committed
375
		keyID = array[5]
376 377 378 379
		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]
380 381 382 383
		resource = array[3]
		if not resource:
			resource = ''
		priority = array[4]
384
		if gajim.jid_is_transport(jid):
385
			# It must be an agent
386
			ji = jid.replace('@', '')
387 388
		else:
			ji = jid
389

nkour's avatar
nkour committed
390
		# Update contact
391
		jid_list = gajim.contacts.get_jid_list(account)
sb's avatar
sb committed
392
		if ji in jid_list or jid == gajim.get_jid_from_account(account):
393
			lcontact = gajim.contacts.get_contacts_from_jid(account, ji)
394
			contact1 = None
395
			resources = []
396
			for c in lcontact:
nkour's avatar
typo  
nkour committed
397
				resources.append(c.resource)
398 399
				if c.resource == resource:
					contact1 = c
400
					break
401 402 403
			if contact1:
				if contact1.show in statuss:
					old_show = statuss.index(contact1.show)
404
				if old_show == new_show and contact1.status == status_message and \
405
					contact1.priority == priority: # no change
406
					return
407
			else:
408
				contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
sb's avatar
sb committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422
				if not contact1:
					# presence of another resource of our jid
					if resource == gajim.connections[account].server_resource:
						return
					contact1 = gajim.contacts.create_contact(jid = ji,
						name = gajim.nicks[account], groups = [],
						show = array[1], status = status_message, sub = 'both',
						ask = 'none', priority = priority, keyID = keyID,
						resource = resource)
					old_show = 0
					gajim.contacts.add_contact(account, contact1)
					lcontact.append(contact1)
					self.roster.add_self_contact(account)
				elif contact1.show in statuss:
423 424 425
					old_show = statuss.index(contact1.show)
				if (resources != [''] and (len(lcontact) != 1 or 
					lcontact[0].show != 'offline')) and jid.find('@') > 0:
426
					old_show = 0
427
					contact1 = gajim.contacts.copy_contact(contact1)
428 429 430
					lcontact.append(contact1)
				contact1.resource = resource
			if contact1.jid.find('@') > 0 and len(lcontact) == 1: # It's not an agent
Yann Leboulanger's avatar
Yann Leboulanger committed
431
				if old_show == 0 and new_show > 1:
432 433 434 435 436 437
					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(5000, self.roster.remove_newly_added,
						contact1.jid, account)
438
				elif old_show > 1 and new_show == 0 and gajim.connections[account].\
439
					connected > 1:
440 441 442 443 444
					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)
445 446
					gobject.timeout_add(5000, self.roster.really_remove_contact,
						contact1, account)
447
			contact1.show = array[1]
448
			contact1.status = status_message
449 450
			contact1.priority = priority
			contact1.keyID = keyID
451 452 453 454 455
			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
456
				contact1.last_status_time = time.localtime()
457
		if gajim.jid_is_transport(jid):
458
			# It must be an agent
459
			if ji in jid_list:
460
				# Update existing iter
461
				self.roster.draw_contact(ji, account)
sb's avatar
sb committed
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
				# transport just signed in/out, don't show popup notifications
				# for 30s
				account_ji = account + '/' + ji
				gajim.block_signed_in_notifications[account_ji] = True
				gobject.timeout_add(30000, self.unblock_signed_in_notifications,
					account_ji)
			locations = (self.instances, self.instances[account])
			for location in locations:
				if location.has_key('add_contact'):
					if old_show == 0 and new_show > 1:
						location['add_contact'].transport_signed_in(jid)
						break
					elif old_show > 1 and new_show == 0:
						location['add_contact'].transport_signed_out(jid)
						break
477
		elif ji in jid_list:
478
			# It isn't an agent
479
			# reset chatstate if needed:
480
			# (when contact signs out or has errors)
481
			if array[1] in ('offline', 'error'):
482 483
				contact1.our_chatstate = contact1.chatstate = \
					contact1.composing_jep = None
484
				gajim.connections[account].remove_transfers_for_contact(contact1)
485 486
			self.roster.chg_contact_status(contact1, array[1], status_message,
				account)
sb's avatar
sb committed
487
			# Notifications
488
			if old_show < 2 and new_show > 1:
489
				notify.notify('contact_connected', jid, account, status_message)
490 491
				if self.remote_ctrl:
					self.remote_ctrl.raise_signal('ContactPresence',
492
						(account, array))
sb's avatar
sb committed
493

494
			elif old_show > 1 and new_show < 2:
495
				notify.notify('contact_disconnected', jid, account, status_message)
496 497 498
				if self.remote_ctrl:
					self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
				# FIXME: stop non active file transfers
499
			elif new_show > 1: # Status change (not connected/disconnected or error (<1))
sb's avatar
sb committed
500 501
				notify.notify('status_change', jid, account, [new_show,
					status_message])
502
		else:
503 504 505
			# FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) doesn't follow the JEP
			# remove in 2007
			# It's maybe a GC_NOTIFY (specialy for MSN gc)
506
			self.handle_event_gc_notify(account, (jid, array[1], status_message,
507
				array[3], None, None, None, None, None, None, None))
508
			
Yann Leboulanger's avatar
Yann Leboulanger committed
509

510
	def handle_event_msg(self, account, array):
511
		# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
sb's avatar
sb committed
512
		# chatstate, msg_id, composing_jep, user_nick)) user_nick is JEP-0172
513

514 515 516
		full_jid_with_resource = array[0]
		jid = gajim.get_jid_without_resource(full_jid_with_resource)
		resource = gajim.get_resource_from_jid(full_jid_with_resource)
517

518
		message = array[1]
519
		msg_type = array[4]
sb's avatar
sb committed
520
		subject = array[5]
nkour's avatar
nkour committed
521
		chatstate = array[6]
522
		msg_id = array[7]
523
		composing_jep = array[8]
524
		if gajim.jid_is_transport(jid):
525
			jid = jid.replace('@', '')
526

Yann Leboulanger's avatar
Yann Leboulanger committed
527 528 529 530 531 532 533
		groupchat_control = self.msg_win_mgr.get_control(jid, account)
		pm = False
		if groupchat_control and groupchat_control.type_id == \
		message_control.TYPE_GC:
			# It's a Private message
			pm = True

Yann Leboulanger's avatar
Yann Leboulanger committed
534 535
		chat_control = None
		jid_of_control = full_jid_with_resource
536 537 538 539 540 541 542
		highest_contact = gajim.contacts.get_contact_with_highest_priority(
			account, jid)
		# Look for a chat control that has the given resource, or default to one
		# without resource
		ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
		if ctrl:
			chat_control = ctrl
Yann Leboulanger's avatar
Yann Leboulanger committed
543
		elif not pm and (not highest_contact or not highest_contact.resource):
544 545 546
			# unknow contact or offline message
			jid_of_control = jid
			chat_control = self.msg_win_mgr.get_control(jid, account)
Yann Leboulanger's avatar
Yann Leboulanger committed
547
		elif highest_contact and resource != highest_contact.resource:
548 549
			jid_of_control = full_jid_with_resource
			chat_control = None
Yann Leboulanger's avatar
Yann Leboulanger committed
550
		elif not pm:
551 552 553
			jid_of_control = jid
			chat_control = self.msg_win_mgr.get_control(jid, account)

554
		# Handle chat states  
555
		contact = gajim.contacts.get_contact(account, jid, resource)
556
		if contact and isinstance(contact, list):
557
			contact = contact[0]
558 559
		if contact:
			contact.composing_jep = composing_jep
560 561 562 563 564 565 566 567 568 569 570 571 572
			if chat_control and chat_control.type_id == message_control.TYPE_CHAT:
				if chatstate is not None:
					# other peer sent us reply, so he supports jep85 or jep22
					contact.chatstate = chatstate
					if contact.our_chatstate == 'ask': # we were jep85 disco?
						contact.our_chatstate = 'active' # no more
					chat_control.handle_incoming_chatstate()
				elif contact.chatstate != 'active':
					# got no valid jep85 answer, peer does not support it
					contact.chatstate = False
			elif chatstate == 'active':
				# Brand new message, incoming.  
				contact.our_chatstate = chatstate
573
				contact.chatstate = chatstate
574 575
				if msg_id: # Do not overwrite an existing msg_id with None
					contact.msg_id = msg_id
576 577 578

		# THIS MUST BE AFTER chatstates handling
		# AND BEFORE playsound (else we here sounding on chatstates!)
579
		if not message: # empty message text
580 581
			return

582 583 584 585
		if gajim.config.get('ignore_unknown_contacts') and \
			not gajim.contacts.get_contact(account, jid) and not pm:
			return

sb's avatar
sb committed
586 587 588
		advanced_notif_num = notify.get_advanced_notification('message_received',
			account, contact)

589
		# Is it a first or next message received ?
590
		first = False
sb's avatar
sb committed
591 592
		if not chat_control and not gajim.events.get_events(account,
		jid_of_control, ['chat']):
593 594 595
			# It's a first message and not a Private Message
			first = True

596
		if pm:
597 598 599 600 601 602
			nickname = resource
			msg_type = 'pm'
			groupchat_control.on_private_message(nickname, message, array[2])
		else:
			# array: (jid, msg, time, encrypted, msg_type, subject)
			self.roster.on_message(jid, message, array[2], account, array[3],
sb's avatar
sb committed
603
				msg_type, subject, resource, msg_id, array[9], advanced_notif_num)
604 605
			nickname = gajim.get_name_from_jid(account, jid)
		# Check and do wanted notifications	
sb's avatar
sb committed
606 607 608 609 610
		msg = message
		if subject:
			msg = _('Subject: %s') % subject + '\n' + msg
		notify.notify('new_message', jid, account, [msg_type, first, nickname,
			msg], advanced_notif_num)
611

612 613
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('NewMessage', (account, array))
614

Yann Leboulanger's avatar
Yann Leboulanger committed
615
	def handle_event_msgerror(self, account, array):
616 617 618
		#'MSGERROR' (account, (jid, error_code, error_msg, msg, time))
		full_jid_with_resource = array[0]
		jids = full_jid_with_resource.split('/', 1)
619
		jid = jids[0]
620
		gcs = self.msg_win_mgr.get_controls(message_control.TYPE_GC)
nicfit's avatar
nicfit committed
621 622 623 624
		for gc_control in gcs:
			if jid == gc_control.contact.jid:
				if len(jids) > 1: # it's a pm
					nick = jids[1]
625
					if not self.msg_win_mgr.get_control(full_jid_with_resource, account):
nicfit's avatar
nicfit committed
626 627 628 629 630 631 632 633 634
						tv = gc_control.list_treeview
						model = tv.get_model()
						i = gc_control.get_contact_iter(nick)
						if i:
							show = model[i][3]
						else:
							show = 'offline'
						gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
							name = nick, show = show)
635
						c = gajim.contacts.contact_from_gc_contact(gc_c)
nicfit's avatar
nicfit committed
636
						self.roster.new_chat(c, account, private_chat = True)
637
					ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
nkour's avatar
nkour committed
638
					ctrl.print_conversation('Error %s: %s' % (array[1], array[2]),
nicfit's avatar
nicfit committed
639 640
								'status')
					return
641

nicfit's avatar
nicfit committed
642 643 644
				gc_control.print_conversation('Error %s: %s' % (array[1], array[2]))
				if gc_control.parent_win.get_active_jid() == jid:
					gc_control.set_subject(gc_control.subject)
645
				return
nicfit's avatar
nicfit committed
646

647
		if gajim.jid_is_transport(jid):
648
			jid = jid.replace('@', '')
649 650 651 652
		msg = array[2]
		if array[3]:
			msg = _('error while sending %s ( %s )') % (array[3], msg)
		self.roster.on_message(jid, msg, array[4], account, \
653
			msg_type='error')
Yann Leboulanger's avatar
Yann Leboulanger committed
654
		
655
	def handle_event_msgsent(self, account, array):
nkour's avatar
nkour committed
656
		#('MSGSENT', account, (jid, msg, keyID))
657 658 659
		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'):
660
			helpers.play_sound('message_sent')
661
		
662
	def handle_event_subscribe(self, account, array):
sb's avatar
sb committed
663 664
		#('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
		dialogs.SubscriptionRequestWindow(array[0], array[1], account, array[2])
665 666
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Subscribe', (account, array))
667 668

	def handle_event_subscribed(self, account, array):
669
		#('SUBSCRIBED', account, (jid, resource))
670
		jid = array[0]
671 672
		if jid in gajim.contacts.get_jid_list(account):
			c = gajim.contacts.get_first_contact_from_jid(account, jid)
673 674
			c.resource = array[1]
			self.roster.remove_contact(c, account)
675 676
			if _('Not in Roster') in c.groups:
				c.groups.remove(_('Not in Roster'))
677
			self.roster.add_contact_to_roster(c.jid, account)
678
		else:
679 680 681 682 683
			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]
684 685
			name = jid.split('@', 1)[0]
			name = name.split('%', 1)[0]
686
			contact1 = gajim.contacts.create_contact(jid = jid, name = name,
687
				groups = [], show = 'online', status = 'online',
688 689
				ask = 'to', resource = array[1], keyID = keyID)
			gajim.contacts.add_contact(account, contact1)
690
			self.roster.add_contact_to_roster(jid, account)
691
		dialogs.InformationDialog(_('Authorization accepted'),
nkour's avatar
nkour committed
692
				_('The contact "%s" has authorized you to see his or her status.')
693
				% jid)
nkour's avatar
nkour committed
694
		if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'):
695
			gajim.connections[account].ack_subscribed(jid)
696 697
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Subscribed', (account, array))
698 699

	def handle_event_unsubscribed(self, account, jid):
nkour's avatar
nkour committed
700
		dialogs.InformationDialog(_('Contact "%s" removed subscription from you') % jid,
nkour's avatar
nkour committed
701
				_('You will always see him or her as offline.'))
702 703
		# FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does not show deny
		gajim.connections[account].ack_unsubscribed(jid)
704 705
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('Unsubscribed', (account, jid))
706 707 708 709 710 711 712 713 714 715 716 717 718 719
	
	def handle_event_agent_info_error(self, account, agent):
		#('AGENT_ERROR_INFO', account, (agent))
		try:
			gajim.connections[account].services_cache.agent_info_error(agent)
		except AttributeError:
			return
	
	def handle_event_agent_items_error(self, account, agent):
		#('AGENT_ERROR_INFO', account, (agent))
		try:
			gajim.connections[account].services_cache.agent_items_error(agent)
		except AttributeError:
			return
720

721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
	def handle_event_agent_removed(self, account, agent):
		# remove transport's contacts from treeview
		jid_list = gajim.contacts.get_jid_list(account)
		for jid in jid_list:
			if jid.endswith('@' + agent):
				c = gajim.contacts.get_first_contact_from_jid(account, jid)
				gajim.log.debug(
					'Removing contact %s due to unregistered transport %s'\
					% (jid, agent))
				gajim.connections[account].unsubscribe(c.jid)
				# Transport contacts can't have 2 resources
				if c.jid in gajim.to_be_removed[account]:
					# This way we'll really remove it
					gajim.to_be_removed[account].remove(c.jid)
				gajim.contacts.remove_jid(account, c.jid)
				self.roster.remove_contact(c, account)

738
	def handle_event_register_agent_info(self, account, array):
739
		#('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
740
		if array[1].has_key('instructions'):
741
			config.ServiceRegistrationWindow(array[0], array[1], account,
742
				array[2])
743
		else:
sb's avatar
sb committed
744 745
			dialogs.ErrorDialog(_('Contact with "%s" cannot be established')\
% array[0], _('Check your connection or try again later.'))
746

747
	def handle_event_agent_info_items(self, account, array):
748
		#('AGENT_INFO_ITEMS', account, (agent, node, items))
749 750 751 752 753
		try:
			gajim.connections[account].services_cache.agent_items(array[0],
				array[1], array[2])
		except AttributeError:
			return
754 755

	def handle_event_agent_info_info(self, account, array):
756 757 758 759 760 761
		#('AGENT_INFO_INFO', account, (agent, node, identities, features, data))
		try:
			gajim.connections[account].services_cache.agent_info(array[0],
				array[1], array[2], array[3], array[4])
		except AttributeError:
			return
762

763
	def handle_event_acc_ok(self, account, array):
764
		#('ACC_OK', account, (config))
765 766
		if self.instances.has_key('account_creation_wizard'):
			self.instances['account_creation_wizard'].acc_is_ok(array)
767

768 769
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('NewAccount', (account, array))
770

771 772
	def handle_event_acc_not_ok(self, account, array):
		#('ACC_NOT_OK', account, (reason))
773 774
		if self.instances.has_key('account_creation_wizard'):
			self.instances['account_creation_wizard'].acc_is_not_ok(array)
775

776
	def handle_event_quit(self, p1, p2):
777
		self.roster.quit_gtkgui_interface()
Yann Leboulanger's avatar
Yann Leboulanger committed
778

Yann Leboulanger's avatar
Yann Leboulanger committed
779
	def handle_event_myvcard(self, account, array):
780
		nick = ''
Yann Leboulanger's avatar
Yann Leboulanger committed
781 782
		if array.has_key('NICKNAME'):
			nick = array['NICKNAME']
783
			if nick:
784
				gajim.nicks[account] = nick
sb's avatar
sb committed
785 786
		if self.instances[account].has_key('profile'):
			win = self.instances[account]['profile']
787 788 789
			win.set_values(array)
			if account in self.show_vcard_when_connect:
				self.show_vcard_when_connect.remove(account)
790

791
	def handle_event_vcard(self, account, vcard):
Yann Leboulanger's avatar
Yann Leboulanger committed
792
		# ('VCARD', account, data)
793 794
		'''vcard holds the vcard data'''
		jid = vcard['jid']
795 796 797
		resource = ''
		if vcard.has_key('resource'):
			resource = vcard['resource']
798 799
		
		# vcard window
Yann Leboulanger's avatar
Yann Leboulanger committed
800
		win = None
801 802
		if self.instances[account]['infos'].has_key(jid):
			win = self.instances[account]['infos'][jid]
803 804
		elif resource and self.instances[account]['infos'].has_key(
			jid + '/' + resource):
805
			win = self.instances[account]['infos'][jid + '/' + resource]
806
		if win:
807
			win.set_values(vcard)
808

809
		# show avatar in chat
810
		win = None
nkour's avatar
nkour committed
811
		ctrl = None
812
		if resource and self.msg_win_mgr.has_window(
813
		jid + '/' + resource, account):
814
			win = self.msg_win_mgr.get_window(jid + '/' + resource,
815 816
				account)
			ctrl = win.get_control(jid + '/' + resource, account)
817 818
		elif self.msg_win_mgr.has_window(jid, account):
			win = self.msg_win_mgr.get_window(jid, account)
819
			ctrl = win.get_control(jid, account)
nkour's avatar
nkour committed
820 821
		if win and ctrl.type_id != message_control.TYPE_GC:
			ctrl.show_avatar()
nicfit's avatar
nicfit committed
822

823
		# Show avatar in roster or gc_roster
824
		gc_ctrl = self.msg_win_mgr.get_control(jid, account)
825
		if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC:
826 827 828
			gc_ctrl.draw_avatar(resource)
		else:
			self.roster.draw_avatar(jid, account)
829 830
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('VcardInfo', (account, vcard))
831

832 833 834 835 836 837 838 839 840 841 842
	def handle_event_last_status_time(self, account, array):
		# ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
		win = None
		if self.instances[account]['infos'].has_key(array[0]):
			win = self.instances[account]['infos'][array[0]]
		elif self.instances[account]['infos'].has_key(array[0] + '/' + array[1]):
			win = self.instances[account]['infos'][array[0] + '/' + array[1]]
		if win:
			c = gajim.contacts.get_contact(account, array[0], array[1])
			# c is a list when no resource is given. it probably means that contact
			# is offline, so only on Contact instance
sb's avatar
sb committed
843
			if isinstance(c, list) and len(c):
844
				c = c[0]
845 846 847 848 849
			if c: # c can be none if it's a gc contact
				c.last_status_time = time.localtime(time.time() - array[2])
				if array[3]:
					c.status = array[3]
				win.set_last_status_time()
850 851 852
		if self.remote_ctrl:
			self.remote_ctrl.raise_signal('LastStatusTime', (account, array))

853
	def handle_event_os_info(self, account, array):
Yann Leboulanger's avatar
Yann Leboulanger committed
854
		win = None
855 856 857 858
		if self.instances[account]['infos'].has_key(array[0]):
			win = self.instances[account]['infos'][array[0]]
		elif self.instances[account]['infos'].has_key(array[0] + '/' + array[1]):
			win = self.instances[account]['infos'][array[0] + '/' + array[1]]
Yann Leboulanger's avatar
Yann Leboulanger committed
859 860
		if win:
			win.set_os_info(array[1], array[2], array[3])
861 862