Newer
Older
## Copyright (C) 2003-2007 Yann Leboulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net>
## Julien Pivotto <roidelapluie@gmail.com>
## Stephan Erb <steve-e@h3c.de>
## 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/>.
##
if os.name == 'nt':
import warnings
warnings.filterwarnings(action='ignore')
# 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'
import sys
import logging
consoleloghandler = logging.StreamHandler()
consoleloghandler.setFormatter(
logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s'))
log = logging.getLogger('gajim')
log.addHandler(consoleloghandler)
log.propagate = False
log = logging.getLogger('gajim.gajim')
# create intermediate loggers
logging.getLogger('gajim.c')
logging.getLogger('gajim.c.x')
import getopt
def parseLogLevel(arg):
if arg.isdigit():
return int(arg)
if arg.isupper():
return getattr(logging, arg)
raise ValueError(_("%s is not a valid loglevel"), repr(arg))
def parseLogTarget(arg):
arg = arg.lower()
if arg.startswith('.'): return arg[1:]
if arg.startswith('gajim'): return arg
return 'gajim.' + arg
def parseAndSetLogLevels(arg):
directive = directive.strip()
targets, level = directive.rsplit('=', 1)
level = parseLogLevel(level.strip())
for target in targets.split('='):
target = parseLogTarget(target.strip())
if target == '':
consoleloghandler.setLevel(level)
print "consoleloghandler level set to %s" % level
else:
logger = logging.getLogger(target)
logger.setLevel(level)
print "Logger %s level set to %d" % (target, level)
def parseOpts():
profile = ''
verbose = False
try:
shortargs = 'hqvl:p:c:'
longargs = 'help quiet verbose loglevel= profile= config_path='
opts, args = getopt.getopt(sys.argv[1:], shortargs, longargs.split())
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]'
sys.exit()
elif o in ('-q', '--quiet'):
consoleloghandler.setLevel(logging.CRITICAL)
verbose = False
elif o in ('-v', '--verbose'):
consoleloghandler.setLevel(logging.INFO)
verbose = True
elif o in ('-p', '--profile'): # gajim --profile name
profile = a
elif o in ('-l', '--loglevel'):
parseAndSetLogLevels(a)
elif o in ('-c', '--config-path'):
config_path = a
return profile, verbose, config_path
profile, verbose, config_path = parseOpts()
del parseOpts, parseAndSetLogLevels, parseLogTarget, parseLogLevel
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
# PyGTK2.10+ only throws a warning
import warnings
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()
warnings.resetwarnings()
from atom_window import AtomWindow
from common import exceptions
from common.zeroconf import connection_zeroconf
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:
pass
pritext = ''
if gtk.pygtk_version < (2, 8, 0):
pritext = _('Gajim needs PyGTK 2.8 or above')
sectext = _('Gajim needs PyGTK 2.8 or above to run. Quiting...')
elif gtk.gtk_version < (2, 8, 0):
pritext = _('Gajim needs GTK 2.8 or above')
sectext = _('Gajim needs GTK 2.8 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'
sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
try:
from common import check_paths
except exceptions.PysqliteNotAvailable, e:
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:
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'
if pritext:
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()
path = os.getcwd()
if '.svn' in os.listdir(path) or '_svn' in os.listdir(path):
# 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

Yann Leboulanger
committed
if not hasattr(gobject, 'timeout_add_seconds'):
def timeout_add_seconds_fake(time_sec, *args):
return gobject.timeout_add(time_sec * 1000, *args)
gobject.timeout_add_seconds = timeout_add_seconds_fake
import re
import signal
import time

Yann Leboulanger
committed
import math
import gtkgui_helpers
import notify
from common.xmpp import idlequeue
from common import nslookup
from common import proxy65_manager
from common import socks5
from common import helpers
from common import optparser
if verbose: gajim.verbose = True
gajimpaths = common.configpaths.gajimpaths
pid_filename = gajimpaths['PID_FILE']
config_filename = gajimpaths['CONFIG_FILE']
import traceback
import errno

Yann Leboulanger
committed
def pid_alive():

Yann Leboulanger
committed
pf = open(pid_filename)
except:
# probably file not found
return False
try:
pid = int(pf.read().strip())
pf.close()
except:
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
if os.name == 'nt':
try:
from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
except:
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)

Yann Leboulanger
committed
if get_p(pid) in ('python.exe', 'gajim.exe'):
return True
return False
elif sys.platform == 'darwin':
from osx import checkPID
return checkPID(pid, 'Gajim.bin')
if not os.path.exists('/proc'):
return True # no /proc, assume Gajim is running
except IOError, e:
if e.errno == errno.ENOENT:
return False # file/pid does not exist

Yann Leboulanger
committed
f.close()
if n.find('gajim') < 0:
return False
return True # Running Gajim found at pid

Yann Leboulanger
committed
except:
traceback.print_exc()
# If we are here, pidfile exists, but some unexpected error occured.
# Assume Gajim is running.
return True

Yann Leboulanger
committed
if pid_alive():
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

Yann Leboulanger
committed
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)
del path_to_file
del pix
del pritext
del sectext

Yann Leboulanger
committed
# 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()

Yann Leboulanger
committed
def on_exit():
# delete pid file on normal exit
if os.path.exists(pid_filename):
os.remove(pid_filename)
if sys.platform == 'darwin':
import osx
osx.shutdown()

Yann Leboulanger
committed
import atexit
atexit.register(on_exit)

nkour
committed
parser = optparser.OptionsParser(config_filename)
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 = {}
# 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,
# 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()
class Interface:

Yann Leboulanger
committed
def handle_event_roster(self, account, data):
self.roster.fill_contacts_and_groups_dicts(data, account)
self.roster.add_account_contacts(account)
self.roster.fire_up_unread_messages_events(account)

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Roster', (account, data))

Yann Leboulanger
committed
def handle_event_warning(self, unused, data):
#('WARNING', account, (title_text, section_text))

Yann Leboulanger
committed
dialogs.WarningDialog(data[0], data[1])

Yann Leboulanger
committed
def handle_event_error(self, unused, data):
#('ERROR', account, (title_text, section_text))

Yann Leboulanger
committed
dialogs.ErrorDialog(data[0], data[1])
def handle_event_information(self, unused, data):
#('INFORMATION', account, (title_text, section_text))
dialogs.InformationDialog(data[0], data[1])
def handle_event_ask_new_nick(self, account, data):
#('ASK_NEW_NICK', account, (room_jid, title_text, prompt_text, proposed_nick))

nkour
committed
room_jid = data[0]
title = data[1]
prompt = data[2]
proposed_nick = data[3]

Yann Leboulanger
committed
gc_control = self.msg_win_mgr.get_control(room_jid, account)
if not gc_control and \
room_jid in self.minimized_controls[account]:
gc_control = self.minimized_controls[account][room_jid]
if gc_control: # user may close the window before we are here
gc_control.show_change_nick_input_dialog(title, prompt, proposed_nick)
def handle_event_http_auth(self, account, data):
#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
def response(account, iq_obj, answer):

Yann Leboulanger
committed
self.dialog.destroy()
gajim.connections[account].build_http_auth_answer(iq_obj, answer)

Yann Leboulanger
committed
def on_yes(is_checked, account, iq_obj):
response(account, iq_obj, 'yes')
if gajim.get_number_of_connected_accounts() > 1:
sec_msg = _('Do you accept this request on account %s?') % account

Yann Leboulanger
committed
self.dialog = dialogs.YesNoDialog(_('HTTP (%s) Authorization for %s (id: %s)') \

Yann Leboulanger
committed
on_response_yes=(on_yes, account, data[3]),
on_response_no=(response, account, data[3], 'no'))
def handle_event_error_answer(self, account, array):
#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
id, jid_from, errmsg, errcode = array
if unicode(errcode) in ('403', '406') and id:
ft = self.instances['file_transfers']
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
conn = gajim.connections[account]
conn.disconnect_transfer(file_props)
return
elif unicode(errcode) == '404':
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]
(jid_from, file_props))
conn.disconnect_transfer(file_props)
return

Yann Leboulanger
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', 'tcp'

Yann Leboulanger
committed
gajim.con_types[account] = con_type
self.roster.draw_account(account)
def handle_event_connection_lost(self, account, array):
# ('CONNECTION_LOST', account, [title, text])
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'connection_lost.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
notify.popup(_('Connection Failed'), account, account,
'connection_failed', path, array[0], array[1])
def unblock_signed_in_notifications(self, account):
gajim.block_signed_in_notifications[account] = False
def handle_event_status(self, account, status): # OUR status
model = self.roster.status_combobox.get_model()
if status == 'offline':

Yann Leboulanger
committed
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

Yann Leboulanger
committed
gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account)
# sensitivity for this menuitem
model[self.roster.status_message_menuitem_iter][3] = True
# Inform all controls for this account of the connection state change
ctrls = self.msg_win_mgr.get_controls()
if self.minimized_controls.has_key(account):
# Can not be the case when we remove account
ctrls += self.minimized_controls[account].values()
for ctrl in ctrls:
if status == 'offline' or (status == 'invisible' and \
gajim.connections[account].is_zeroconf):
ctrl.got_disconnected()
else:
# Other code rejoins all GCs, so we don't do it here
if not ctrl.type_id == message_control.TYPE_GC:
ctrl.got_connected()
if ctrl.parent_win:
ctrl.parent_win.redraw_tab(ctrl)
self.roster.on_status_changed(account, status)

Yann Leboulanger
committed
if account in self.show_vcard_when_connect:
self.edit_own_details(account)

nkour
committed
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 not self.instances[account].has_key('profile'):
self.instances[account]['profile'] = \
profile_window.ProfileWindow(account)
gajim.connections[account].request_vcard(jid)
def handle_event_notify(self, account, array):
# 'NOTIFY' (account, (jid, status, status message, resource, priority,
# if we're here it means contact changed show
statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
# Ignore invalid show
if array[1] not in statuss:
return
status_message = array[2]
jid = array[0].split('/')[0]
# Get the proper keyID
keyID = helpers.prepare_and_validate_gpg_keyID(account,
jid, keyID)
resource = array[3]
if not resource:
resource = ''
priority = array[4]
if gajim.jid_is_transport(jid):
# It must be an agent
ji = jid.replace('@', '')
jid_list = gajim.contacts.get_jid_list(account)

Yann Leboulanger
committed
if ji in jid_list or jid == gajim.get_jid_from_account(account):
lcontact = gajim.contacts.get_contacts(account, ji)
contact1 = None
for c in lcontact:
if c.resource == resource:
contact1 = c
if contact1:
if contact1.show in statuss:
old_show = statuss.index(contact1.show)
if contact_nickname is not None and \
contact1.contact_name != contact_nickname:
contact1.contact_name = contact_nickname
self.roster.draw_contact(jid, account)
if old_show == new_show and contact1.status == status_message and \

Yann Leboulanger
committed
return
contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)

Yann Leboulanger
committed
if not contact1:
# presence of another resource of our jid
if resource == gajim.connections[account].server_resource:
return

Yann Leboulanger
committed
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:
old_show = statuss.index(contact1.show)
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
if contact1.jid.find('@') > 0 and len(lcontact) == 1:
# It's not an agent
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)

Yann Leboulanger
committed
gobject.timeout_add_seconds(5, self.roster.remove_newly_added,
contact1.jid, account)
elif old_show > 1 and new_show == 0 and gajim.connections[account].\
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)

Yann Leboulanger
committed
gobject.timeout_add_seconds(5, self.roster.really_remove_contact,

Yann Leboulanger
committed
contact1, account)
contact1.show = array[1]
contact1.status = status_message
contact1.priority = priority
contact1.keyID = keyID
timestamp = array[6]
if timestamp:
contact1.last_status_time = timestamp
elif not gajim.block_signed_in_notifications[account]:
# We're connected since more that 30 seconds

Yann Leboulanger
committed
contact1.last_status_time = time.localtime()
if gajim.jid_is_transport(jid):
# It must be an agent
# Update existing iter
self.roster.draw_contact(ji, account)
self.roster.draw_group(_('Transports'), account)

Yann Leboulanger
committed
if new_show > 1 and ji in gajim.transport_avatar[account]:
# transport just signed in. request avatars
for jid_ in gajim.transport_avatar[account][ji]:
gajim.connections[account].request_vcard(jid_)

Yann Leboulanger
committed
# transport just signed in/out, don't show popup notifications
# for 30s
account_ji = account + '/' + ji
gajim.block_signed_in_notifications[account_ji] = True

Yann Leboulanger
committed
gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications,

Yann Leboulanger
committed
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
# reset chatstate if needed:
# (when contact signs out or has errors)
if array[1] in ('offline', 'error'):

Yann Leboulanger
committed
contact1.our_chatstate = contact1.chatstate = \
gajim.connections[account].remove_transfers_for_contact(contact1)
self.roster.chg_contact_status(contact1, array[1], status_message,
account)
if old_show < 2 and new_show > 1:
notify.notify('contact_connected', jid, account, status_message)

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactPresence',

Yann Leboulanger
committed
elif old_show > 1 and new_show < 2:
notify.notify('contact_disconnected', jid, account, status_message)

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
# FIXME: stop non active file transfers
elif new_show > 1: # Status change (not connected/disconnected or error (<1))
notify.notify('status_change', jid, account, [new_show,
status_message])
else:
# FIXME: Msn transport (CMSN1.2.1 and PyMSN) doesn'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))
def handle_event_msg(self, account, array):

nkour
committed
# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
# chatstate, msg_id, composing_xep, user_nick, xhtml, session, form_node))

nkour
committed
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)

Yann Leboulanger
committed

nkour
committed
message = array[1]
msg_type = array[4]

Yann Leboulanger
committed
msg_id = array[7]
if gajim.config.get('ignore_incoming_xhtml'):
xhtml = None
if gajim.jid_is_transport(jid):
jid = jid.replace('@', '')
groupchat_control = self.msg_win_mgr.get_control(jid, account)
jid in self.minimized_controls[account]:
groupchat_control = self.minimized_controls[account][jid]
pm = False
if groupchat_control and groupchat_control.type_id == \
message_control.TYPE_GC:
# It's a Private message
pm = True
chat_control = None
jid_of_control = full_jid_with_resource

Yann Leboulanger
committed
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
elif not pm and (not highest_contact or not highest_contact.resource):

Yann Leboulanger
committed
# unknow contact or offline message
jid_of_control = jid
chat_control = self.msg_win_mgr.get_control(jid, account)
elif highest_contact and resource != highest_contact.resource and \
highest_contact.show != 'offline':

Yann Leboulanger
committed
jid_of_control = full_jid_with_resource
chat_control = None

Yann Leboulanger
committed
jid_of_control = jid
chat_control = self.msg_win_mgr.get_control(jid, account)

Yann Leboulanger
committed
contact = gajim.contacts.get_contact(account, jid, resource)
if contact:
if contact.composing_xep != 'XEP-0085': # We cache xep85 support
contact.composing_xep = composing_xep

Yann Leboulanger
committed
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':

Yann Leboulanger
committed
contact.our_chatstate = chatstate
contact.chatstate = chatstate

Yann Leboulanger
committed
if msg_id: # Do not overwrite an existing msg_id with None
contact.msg_id = msg_id
# THIS MUST BE AFTER chatstates handling
# AND BEFORE playsound (else we ear sounding on chatstates!)

nkour
committed
if not message: # empty message text
return

jimpp
committed
if gajim.config.get('ignore_unknown_contacts') and \
not gajim.contacts.get_contacts(account, jid) and not pm:

jimpp
committed
return
if not contact:
# contact is not in the roster, create a fake one to display
# notification
contact = common.contacts.Contact(jid = jid, resource = resource)
advanced_notif_num = notify.get_advanced_notification('message_received',
account, contact)

jimpp
committed
# Is it a first or next message received ?

Yann Leboulanger
committed
first = False
if msg_type == 'normal':
if not gajim.events.get_events(account, jid, ['normal']):
first = True
elif not chat_control and not gajim.events.get_events(account,
jid_of_control, [msg_type]): # msg_type can be chat or pm

Yann Leboulanger
committed
first = True

nkour
committed
if pm:

jimpp
committed
nickname = resource
groupchat_control.on_private_message(nickname, message, array[2],

jimpp
committed
else:
# array: (jid, msg, time, encrypted, msg_type, subject)
if encrypted:
self.roster.on_message(jid, message, array[2], account, array[3],
msg_type, subject, resource, msg_id, array[9],
advanced_notif_num, session=session, form_node=array[12])
else:
# xhtml in last element
self.roster.on_message(jid, message, array[2], account, array[3],
msg_type, subject, resource, msg_id, array[9],
advanced_notif_num, xhtml=xhtml, session=session,
form_node=array[12])

jimpp
committed
nickname = gajim.get_name_from_jid(account, jid)
msg = message
if subject:
msg = _('Subject: %s') % subject + '\n' + msg

Yann Leboulanger
committed
focused = False
if chat_control:
parent_win = chat_control.parent_win
if chat_control == parent_win.get_active_control() and \
parent_win.window.has_focus:
focused = True
notify.notify('new_message', jid_of_control, account, [msg_type,

Yann Leboulanger
committed
first, nickname, msg, focused], advanced_notif_num)

jimpp
committed

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('NewMessage', (account, array))
def handle_event_msgerror(self, account, array):

nkour
committed
#'MSGERROR' (account, (jid, error_code, error_msg, msg, time))
full_jid_with_resource = array[0]
jids = full_jid_with_resource.split('/', 1)
gc_control = self.msg_win_mgr.get_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 not 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.msg_win_mgr.get_control(full_jid_with_resource, account)
ctrl.print_conversation('Error %s: %s' % (array[1], array[2]),
'status')
gc_control.print_conversation('Error %s: %s' % (array[1], array[2]))
if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid:
gc_control.set_subject(gc_control.subject)
return
if gajim.jid_is_transport(jid):
jid = jid.replace('@', '')
msg = array[2]
if array[3]:
msg = _('error while sending %s ( %s )') % (array[3], msg)
self.roster.on_message(jid, msg, array[4], account, \
msg_type='error')
def handle_event_msgsent(self, account, array):
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')

Yann Leboulanger
committed
def handle_event_msgnotsent(self, account, array):
#('MSGNOTSENT', account, (jid, ierror_msg, msg, time))
msg = _('error while sending %s ( %s )') % (array[2], array[1])
self.roster.on_message(array[0], msg, array[3], account,
msg_type='error')
def handle_event_subscribe(self, account, array):
#('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
dialogs.SubscriptionRequestWindow(array[0], array[1], account, array[2])

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Subscribe', (account, array))
def handle_event_subscribed(self, account, array):
#('SUBSCRIBED', account, (jid, resource))
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(c, account)
if _('Not in Roster') in c.groups:
c.groups.remove(_('Not in Roster'))
self.roster.add_contact_to_roster(c.jid, account)
keyID = ''
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split()
if jid in attached_keys:
keyID = attached_keys[attached_keys.index(jid) + 1]
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_to_roster(jid, account)

nkour
committed
dialogs.InformationDialog(_('Authorization accepted'),
_('The contact "%s" has authorized you to see his or her status.')
if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'):
gajim.connections[account].ack_subscribed(jid)

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Subscribed', (account, array))
def handle_event_unsubscribed(self, account, jid):
gajim.connections[account].ack_unsubscribed(jid)

nkour
committed
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Unsubscribed', (account, jid))
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
if not contact:
return

Yann Leboulanger
committed
def on_yes(is_checked, list_):
self.roster.on_req_usub(None, list_)
list_ = [(contact, account)]
dialogs.YesNoDialog(
_('Contact "%s" removed subscription from you') % jid,
_('You will always see him or her as offline.\nDo you want to remove him or her from your contact list?'),

Yann Leboulanger
committed
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_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

Yann Leboulanger
committed
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'\