Skip to content
Snippets Groups Projects
gtkgui.py 34.9 KiB
Newer Older
Yann Leboulanger's avatar
Yann Leboulanger committed
##	plugins/gtkgui.py
##
## Gajim Team:
nkour's avatar
nkour committed
## - Yann Le Boulanger <asterix@lagaule.org>
## - Vincent Hanquez <tab@snarc.org>
##	- Nikos Kouremenos <kourem@gmail.com>
Yann Leboulanger's avatar
Yann Leboulanger committed
##
Yann Leboulanger's avatar
Yann Leboulanger committed
##	Copyright (C) 2003-2005 Gajim Team
Yann Leboulanger's avatar
Yann Leboulanger committed
##
## 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.
##

nkour's avatar
nkour committed
if __name__ == "__main__":
	import getopt, pickle, sys, socket

	try: 	# Import Psyco if available
		import psyco
		psyco.full()
	except ImportError:
		pass
		
nkour's avatar
nkour committed
	try:
		opts, args = getopt.getopt(sys.argv[1:], "p:h", ["help"])
	except getopt.GetoptError:
		# print help information and exit:
		usage()
		sys.exit(2)
	port = 8255
	for o, a in opts:
		if o == '-p':
			port = a
		if o in ("-h", "--help"):
			usage()
			sys.exit()
	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	try:
		sock.connect(('', 8255))
	except:
		#TODO: use i18n
		print "unable to connect to localhost on port ", port
	else:
		evp = pickle.dumps(('EXEC_PLUGIN', '', 'gtkgui'))
		sock.send('<'+evp+'>')
		sock.close()
	sys.exit()

Yann Leboulanger's avatar
Yann Leboulanger committed
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import os
import time
import sys
nkour's avatar
nkour committed
import Queue
Yann Leboulanger's avatar
Yann Leboulanger committed
import sre
try:
	import winsound # windows-only built-in module for playing wav
except ImportError:
	pass

class CellRendererImage(gtk.GenericCellRenderer):

	__gproperties__ = {
		"image": (gobject.TYPE_OBJECT, "Image", 
		"Image", gobject.PARAM_READWRITE),
	}
	def __init__(self):
		self.__gobject_init__()
		self.image = None

	def do_set_property(self, pspec, value):
		setattr(self, pspec.name, value)

	def do_get_property(self, pspec):
		return getattr(self, pspec.name)

	def func(self, model, path, iter, (image, tree)):
		if model.get_value(iter, 0) == image:
			self.redraw = 1
			cell_area = tree.get_cell_area(path, tree.get_column(0))
			tree.queue_draw_area(cell_area.x, cell_area.y, cell_area.width, \
				cell_area.height)
	def animation_timeout(self, tree, image):
		if image.get_storage_type() == gtk.IMAGE_ANIMATION:
			model = tree.get_model()
			model.foreach(self.func, (image, tree))
			if self.redraw:
				gobject.timeout_add(image.get_data('iter').get_delay_time(), \
					self.animation_timeout, tree, image)
			else:
				image.set_data('iter', None)
				
	def on_render(self, window, widget, background_area,cell_area, \
		expose_area, flags):
		if not self.image:
			return
		pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = \
			self.on_get_size(widget, cell_area)

		pix_rect.x += cell_area.x
		pix_rect.y += cell_area.y
		pix_rect.width  -= 2 * self.get_property("xpad")
		pix_rect.height -= 2 * self.get_property("ypad")

		draw_rect = cell_area.intersect(pix_rect)
		draw_rect = expose_area.intersect(draw_rect)
		if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
			if not self.image.get_data('iter'):
				animation = self.image.get_animation()
				self.image.set_data('iter', animation.get_iter())
				gobject.timeout_add(self.image.get_data('iter').get_delay_time(), \
					self.animation_timeout, widget, self.image)

			pix = self.image.get_data('iter').get_pixbuf()
		elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
			pix = self.image.get_pixbuf()
		else:
			return
		window.draw_pixbuf(widget.style.black_gc, pix, \
			draw_rect.x-pix_rect.x, draw_rect.y-pix_rect.y, draw_rect.x, \
			draw_rect.y+2, draw_rect.width, draw_rect.height, \
			gtk.gdk.RGB_DITHER_NONE, 0, 0)
		if not self.image:
			return 0, 0, 0, 0
		if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
			animation = self.image.get_animation()
			pix = animation.get_iter().get_pixbuf()
		elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
			pix = self.image.get_pixbuf()
		else:
			return 0, 0, 0, 0
		pixbuf_width  = pix.get_width()
		pixbuf_height = pix.get_height()
		calc_width  = self.get_property("xpad") * 2 + pixbuf_width
		calc_height = self.get_property("ypad") * 2 + pixbuf_height
		x_offset = 0
		y_offset = 0
		if cell_area and pixbuf_width > 0 and pixbuf_height > 0:
			x_offset = self.get_property("xalign") * (cell_area.width - \
				calc_width -  self.get_property("xpad"))
			y_offset = self.get_property("yalign") * (cell_area.height - \
				calc_height -  self.get_property("ypad"))
		return x_offset, y_offset, calc_width, calc_height

gobject.type_register(CellRendererImage)
nkour's avatar
nkour committed
class User:
	"""Information concerning each users"""
Yann Leboulanger's avatar
Yann Leboulanger committed
	def __init__(self, *args):
		if len(args) == 0:
			self.jid = ''
			self.name = ''
			self.groups = []
			self.show = ''
			self.status = ''
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.sub = ''
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.resource = ''
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.keyID = ''
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.jid = args[0]
			self.name = args[1]
			self.groups = args[2]
			self.show = args[3]
			self.status = args[4]
			self.sub = args[5]
			self.ask = args[6]
			self.resource = args[7]
			self.priority = args[8]
			self.keyID = args[9]
		else: raise TypeError, _('bad arguments')
from tabbed_chat_window import *
from groupchat_window import *
from history_window import *
from roster_window import *
from systray import *
from dialogs import *
from config import *
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
def usage():
	#TODO: use i18n
	print 'usage :', sys.argv[0], ' [OPTION]'
	print '  -p\tport on which the sock plugin listen'
	print '  -h, --help\tdisplay this help and exit'
GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade'
Yann Leboulanger's avatar
Yann Leboulanger committed
class plugin:
	"""Class called by the core in a new thread"""
Yann Leboulanger's avatar
Yann Leboulanger committed

	class accounts:
		"""Class where are stored the accounts and users in them"""
		def __init__(self):
			self.__accounts = {}

		def add_account(self, account, users=()):
			#users must be like (user1, user2)
			self.__accounts[account] = users

		def add_user_to_account(self, account, user):
			if self.__accounts.has_key(account):
				self.__accounts[account].append(user)
			else :
				return 1

		def get_accounts(self):
			return self.__accounts.keys();

		def get_users(self, account):
			if self.__accounts.has_key(account):
				return self.__accounts[account]
			else :
				return None

		def which_account(self, user):
			for a in self.__accounts.keys():
				if user in self.__accounts[a]:
					return a
			return None

	def launch_browser_mailer(self, kind, url):
		#kind = 'url' or 'mail'
		if self.config['openwith'] == 'gnome-open':
			app = 'gnome-open'
			args = ['gnome-open']
			args.append(url)
		elif self.config['openwith'] == 'kfmclient exec':
			app = 'kfmclient'
			args = ['kfmclient', 'exec']
		elif self.config['openwith'] == 'custom':
			if kind == 'url':
				conf = self.config['custombrowser']
			if kind == 'mail':
				conf = self.config['custommailapp']
			if conf == '': # if no app is configured
				return
			args = conf.split()
			app = args[0]
		args.append(url)
		try:
			if os.name == 'posix':
				os.spawnvp(os.P_NOWAIT, app, args)
			else:
				os.spawnv(os.P_NOWAIT, app, args)
		except:
			pass
	def play_timeout(self, pid):
		pidp, r = os.waitpid(pid, os.WNOHANG)
		return 0

	def play_sound(self, event):
		if not self.config['sounds_on']:
		path_to_soundfile = self.config[event + '_file']
		if not os.path.exists(path_to_soundfile):
Yann Leboulanger's avatar
Yann Leboulanger committed
			return
		if os.name  == 'nt':
			winsound.PlaySound(path_to_soundfile, \
									winsound.SND_FILENAME|winsound.SND_ASYNC)
		elif os.name == 'posix':
			if self.config['soundplayer'] == '':
				return
			argv = self.config['soundplayer'].split()
			argv.append(path_to_soundfile)
			pid = os.spawnvp(os.P_NOWAIT, argv[0], argv)
			pidp, r = os.waitpid(pid, os.WNOHANG)
			if pidp == 0:
				gobject.timeout_add(10000, self.play_timeout, pid)
Yann Leboulanger's avatar
Yann Leboulanger committed
	def send(self, event, account, data):
		self.queueOUT.put((event, account, data))

	def wait(self, what):
		"""Wait for a message from Core"""
Yann Leboulanger's avatar
Yann Leboulanger committed
		#TODO: timeout
		temp_q = Queue.Queue(50)
		while 1:
			if not self.queueIN.empty():
				ev = self.queueIN.get()
Yann Leboulanger's avatar
Yann Leboulanger committed
				if ev[0] == what and ev[2][0] == 'GtkGui':
Yann Leboulanger's avatar
Yann Leboulanger committed
					#Restore messages
					while not temp_q.empty():
						ev2 = temp_q.get()
Yann Leboulanger's avatar
Yann Leboulanger committed
						self.queueIN.put(ev2)
Yann Leboulanger's avatar
Yann Leboulanger committed
					return ev[2][1]
Yann Leboulanger's avatar
Yann Leboulanger committed
				else:
					#Save messages
					temp_q.put(ev)
	def handle_event_roster(self, account, data):
		#('ROSTER', account, (state, array))
		statuss = ['offline', 'online', 'away', 'xa', 'dnd', 'invisible']
		self.roster.on_status_changed(account, statuss[data[0]])
		self.roster.mklists(data[1], account)
		self.roster.draw_roster()
	
Vincent Hanquez's avatar
Vincent Hanquez committed
	def handle_event_warning(self, unused, msg):
		Warning_dialog(msg)
	def handle_event_error(self, unused, msg):
		Error_dialog(msg)
	
	def handle_event_status(self, account, status): # OUR status
		#('STATUS', account, status)
		self.roster.on_status_changed(account, status)
	
	def handle_event_notify(self, account, array):
Yann Leboulanger's avatar
Yann Leboulanger committed
		#('NOTIFY', account, (jid, status, message, resource, priority, keyID, 
		# role, affiliation, real_jid, reason, actor, statusCode))
		statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd', 'invisible']
		new_show = statuss.index(array[1])
Yann Leboulanger's avatar
Yann Leboulanger committed
		keyID = array[5]
		resource = array[3]
		if not resource:
			resource = ''
		priority = array[4]
			#It must be an agent
		else:
			ji = jid
		#Update user
		if self.roster.contacts[account].has_key(ji):
			luser = self.roster.contacts[account][ji]
			user1 = None
			resources = []
			for u in luser:
				resources.append(u.resource)
				if u.resource == resource:
					user1 = u
					break
Yann Leboulanger's avatar
Yann Leboulanger committed
				if user1.show in statuss:
					old_show = statuss.index(user1.show)
				user1 = self.roster.contacts[account][ji][0]
Yann Leboulanger's avatar
Yann Leboulanger committed
				if user1.show in statuss:
					old_show = statuss.index(user1.show)
				if (resources != [''] and (len(luser) != 1 or 
					luser[0].show != 'offline')) and not jid.find("@") <= 0:
nkour's avatar
nkour committed
					user1 = User(user1.jid, user1.name, user1.groups, user1.show, \
					user1.status, user1.sub, user1.ask, user1.resource, \
Yann Leboulanger's avatar
Yann Leboulanger committed
						user1.priority, user1.keyID)
					luser.append(user1)
				user1.resource = resource
			if user1.jid.find('@') > 0: # It's not an agent
				if old_show == 0 and new_show > 1:
					if not user1.jid in self.roster.newly_added[account]:
						self.roster.newly_added[account].append(user1.jid)
					if user1.jid in self.roster.to_be_removed[account]:
						self.roster.to_be_removed[account].remove(user1.jid)
					gobject.timeout_add(5000, self.roster.remove_newly_added, \
						user1.jid, account)
				if old_show > 1 and new_show == 0 and self.connected[account] > 1:
					if not user1.jid in self.roster.to_be_removed[account]:
						self.roster.to_be_removed[account].append(user1.jid)
					if user1.jid in self.roster.newly_added[account]:
						self.roster.newly_added[account].remove(user1.jid)
					self.roster.redraw_jid(user1.jid, account)
					if not self.queues[account].has_key(jid):
						gobject.timeout_add(5000, self.roster.really_remove_user, \
							user1, account)
			user1.show = array[1]
			user1.status = array[2]
			user1.priority = priority
Yann Leboulanger's avatar
Yann Leboulanger committed
			user1.keyID = keyID
			#It must be an agent
			if self.roster.contacts[account].has_key(ji):
				#Update existing iter
				self.roster.redraw_jid(ji, account)
		elif self.roster.contacts[account].has_key(ji):
			#It isn't an agent
			self.roster.chg_user_status(user1, array[1], array[2], account)
			if old_show < 2 and new_show > 1 and \
				self.config['sound_contact_connected']:
				self.play_sound('sound_contact_connected')
				if not self.windows[account]['chats'].has_key(jid) and \
					not self.queues[account].has_key(jid) and \
											not self.config['autopopup']:
					#FIXME:
					#DOES NOT ALWAYS WORK WHY?
					#I control nkour@lagaule in jabber
					# have nkour@lagaul in nkour@jabber.org
					#go online from psi in lagaule
					#gajim doesn't give a shit
					# WHY? same with offline
					# new message works
					instance = Popup_window(self, 'Contact Online', jid, account)
					self.roster.popup_windows.append(instance)
			elif old_show > 1 and new_show < 2 and \
				self.config['sound_contact_disconnected']:
				self.play_sound('sound_contact_disconnected')
				if not self.windows[account]['chats'].has_key(jid) and \
							not self.queues[account].has_key(jid) and \
											not self.config['autopopup']:
					instance = Popup_window(self, 'Contact Offline', jid, account)
					self.roster.popup_windows.append(instance)
		elif self.windows[account]['gc'].has_key(ji):
			#it is a groupchat presence
			self.windows[account]['gc'][ji].chg_user_status(ji, resource, \
				array[1], array[2], array[6], array[7], array[8], array[9], \
				array[10], array[11], account)
	def handle_event_msg(self, account, array):
		#('MSG', account, (user, msg, time))
		jid = array[0].split('/')[0]
		if jid.find("@") <= 0:
			jid = jid.replace('@', '')
		if self.config['ignore_unknown_contacts'] and \
			not self.roster.contacts[account].has_key(jid):
			return
Yann Leboulanger's avatar
Yann Leboulanger committed
		if not self.windows[account]['chats'].has_key(jid) and \
						not self.queues[account].has_key(jid):
			first = True
			if	not self.config['autopopup']:
				instance = Popup_window(self, 'New Message', jid, account)
				self.roster.popup_windows.append(instance)
		self.roster.on_message(jid, array[1], array[2], account)
Yann Leboulanger's avatar
Yann Leboulanger committed
		if self.config['sound_first_message_received'] and first:
			self.play_sound('sound_first_message_received')
		if self.config['sound_next_message_received'] and not first:
			self.play_sound('sound_next_message_received')
	def handle_event_msgerror(self, account, array):
		#('MSGERROR', account, (user, error_code, error_msg, msg, time))
		jid = array[0].split('/')[0]
		if jid.find("@") <= 0:
			jid = jid.replace('@', '')
		self.roster.on_message(jid, _("error while sending") + \
			' \"%s\" ( %s )' % (array[3], array[2]), array[4], account)
	def handle_event_msgsent(self, account, array):
		#('MSG', account, (jid, msg, keyID))
		if self.config['sound_message_sent']:
			self.play_sound('sound_message_sent')
	def handle_event_subscribe(self, account, array):
		#('SUBSCRIBE', account, (jid, text))
Yann Leboulanger's avatar
Yann Leboulanger committed
		subscription_request_window(self, array[0], array[1], account)

	def handle_event_subscribed(self, account, array):
		#('SUBSCRIBED', account, (jid, resource))
		jid = array[0]
		if self.roster.contacts[account].has_key(jid):
			u = self.roster.contacts[account][jid][0]
			u.resource = array[1]
nkour's avatar
nkour committed
			if 'not in the roster' in u.groups:
				u.groups.remove('not in the roster')
				u.groups = ['General']
			self.roster.add_user_to_roster(u.jid, account)
			self.send('UPDUSER', account, (u.jid, u.name, u.groups))
			user1 = User(jid, jid, ['General'], 'online', \
				'online', 'to', '', array[1], 0, '')
			self.roster.contacts[account][jid] = [user1]
			self.roster.add_user_to_roster(jid, account)
		Information_dialog(_("You are now authorized by %s") % jid)

	def handle_event_unsubscribed(self, account, jid):
		Information_dialog(_("You are now unsubscribed by %s") % jid)
	def handle_event_agents(self, account, agents):
		#('AGENTS', account, agents)
		if self.windows[account].has_key('disco'):
			self.windows[account]['disco'].agents(agents)

	def handle_event_agent_info(self, account, array):
		#('AGENT_INFO', account, (agent, identities, features, items))
		if self.windows[account].has_key('disco'):
			self.windows[account]['disco'].agent_info(array[0], array[1], \
	def handle_event_agent_info_items(self, account, array):
		#('AGENT_INFO_ITEMS', account, (agent, items))
		if self.windows[account].has_key('disco'):
			self.windows[account]['disco'].agent_info_items(array[0], array[1])

	def handle_event_agent_info_info(self, account, array):
		#('AGENT_INFO_INFO', account, (agent, identities, features))
		if self.windows[account].has_key('disco'):
			self.windows[account]['disco'].agent_info_info(array[0], array[1], \
	def handle_event_reg_agent_info(self, account, array):
		#('REG_AGENTS_INFO', account, (agent, infos))
		if not array[1].has_key('instructions'):
			Error_dialog(_("error contacting %s") % array[0])
			Service_registration_window(array[0], array[1], self, account)

	def handle_event_acc_ok(self, account, array):
nkour's avatar
nkour committed
		#('ACC_OK', account, (hostname, login, pasword, name, resource, prio,
		#use_proxy, proxyhost, proxyport))
		if self.windows['account_modification']:
			self.windows['account_modification'].account_is_ok(array[1])
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.accounts[name] = {'name': array[1], \
				'hostname': array[0],\
				'password': array[2],\
				'resource': array[4],\
				'priority': array[5],\
				'use_proxy': array[6],\
				'proxyhost': array[7], \
				'proxyport': array[8]}
			self.send('CONFIG', None, ('accounts', self.accounts, 'GtkGui'))
		self.windows[name] = {'infos': {}, 'chats': {}, 'gc': {}}
		self.queues[name] = {}
		self.connected[name] = 0
		self.roster.groups[name] = {}
		self.roster.contacts[name] = {}
		if self.windows.has_key('accounts'):
			self.windows['accounts'].init_accounts()
		self.roster.draw_roster()

	def handle_event_quit(self, p1, p2):
		self.roster.on_quit() # SUCH FUNCTION DOES NOT EXIST!!

	def save_config(self):
		hidden_lines = self.config['hiddenlines'].split('\t')
		self.config['hiddenlines'] = '\t'.join(hidden_lines)
		self.send('CONFIG', None, ('GtkGui', self.config, 'GtkGui'))
Yann Leboulanger's avatar
Yann Leboulanger committed
	def handle_event_myvcard(self, account, array):
Yann Leboulanger's avatar
Yann Leboulanger committed
		if array.has_key('NICKNAME'):
			nick = array['NICKNAME']
		if nick == '':
Yann Leboulanger's avatar
Yann Leboulanger committed
			nick = self.accounts[account]['name']
		self.nicks[account] = nick
Yann Leboulanger's avatar
Yann Leboulanger committed
	def handle_event_vcard(self, account, array):
		if self.windows[account]['infos'].has_key(array['jid']):
			self.windows[account]['infos'][array['jid']].set_values(array)
	def handle_event_os_info(self, account, array):
		if self.windows[account]['infos'].has_key(array[0]):
			self.windows[account]['infos'][array[0]].set_os_info(array[1], \
				array[2])

	def handle_event_log_nb_line(self, account, array):
		#('LOG_NB_LINE', account, (jid, nb_line))
		if self.windows['logs'].has_key(array[0]):
			self.windows['logs'][array[0]].set_nb_line(array[1])
			begin = 0
			if array[1] > 50:
				begin = array[1] - 50
			self.send('LOG_GET_RANGE', None, (array[0], begin, array[1]))

	def handle_event_log_line(self, account, array):
		#('LOG_LINE', account, (jid, num_line, date, type, data))
		# if type = 'recv' or 'sent' data = [msg]
		# else type = jid and data = [status, away_msg]
		if self.windows['logs'].has_key(array[0]):
			self.windows['logs'][array[0]].new_line(array[1:])

	def handle_event_gc_msg(self, account, array):
		#('GC_MSG', account, (jid, msg, time))
		jid = jids[0]
		if not self.windows[account]['gc'].has_key(jid):
			return
		if len(jids) == 1:
			#message from server
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.windows[account]['gc'][jid].print_conversation(array[1], jid, \
		else:
			#message from someone
Yann Leboulanger's avatar
Yann Leboulanger committed
			self.windows[account]['gc'][jid].print_conversation(array[1], jid, \
	def handle_event_gc_subject(self, account, array):
		#('GC_SUBJECT', account, (jid, subject))
		jid = jids[0]
		if not self.windows[account]['gc'].has_key(jid):
			return
		self.windows[account]['gc'][jid].set_subject(jid, array[1])
		if len(jids) > 1:
			self.windows[account]['gc'][jid].print_conversation(\
				'%s has set the subject to %s' % (jids[1], array[1]), jid)
	def handle_event_bad_passphrase(self, account, array):
		Warning_dialog(_("Your GPG passphrase is wrong, so you are connected without your GPG key."))

	def handle_event_gpg_secrete_keys(self, account, keys):
		keys['None'] = 'None'
		if self.windows.has_key('gpg_keys'):
			self.windows['gpg_keys'].fill_tree(keys)

	def handle_event_roster_info(self, account, array):
		#('ROSTER_INFO', account, (jid, name, sub, ask, groups))
		jid = array[0]
		if not self.roster.contacts[account].has_key(jid):
			return
		users = self.roster.contacts[account][jid]
		if not (array[2] or array[3]):
			self.roster.remove_user(users[0], account)
			del self.roster.contacts[account][jid]
			#TODO if it was the only one in its group, remove the group
			return
		for user in users:
			name = array[1]
			if array[4]:
				user.groups = array[4]
Yann Leboulanger's avatar
Yann Leboulanger committed
	def read_queue(self):
		"""Read queue from the core and execute commands from it"""
Yann Leboulanger's avatar
Yann Leboulanger committed
		while self.queueIN.empty() == 0:
			ev = self.queueIN.get()
			if ev[0] == 'ROSTER':
Vincent Hanquez's avatar
Vincent Hanquez committed
				self.handle_event_roster(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'WARNING':
Vincent Hanquez's avatar
Vincent Hanquez committed
				self.handle_event_warning(ev[1], ev[2])
			elif ev[0] == 'ERROR':
				self.handle_event_error(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'STATUS':
				self.handle_event_status(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'NOTIFY':
				self.handle_event_notify(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'MSG':
				self.handle_event_msg(ev[1], ev[2])
			elif ev[0] == 'MSGERROR':
				self.handle_event_msgerror(ev[1], ev[2])
			elif ev[0] == 'MSGSENT':
				self.handle_event_msgsent(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'SUBSCRIBE':
				self.handle_event_subscribe(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'SUBSCRIBED':
				self.handle_event_subscribed(ev[1], ev[2])
				self.handle_event_unsubscribed(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'AGENTS':
				self.handle_event_agents(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'AGENT_INFO':
				self.handle_event_agent_info(ev[1], ev[2])
			elif ev[0] == 'AGENT_INFO_ITEMS':
				self.handle_event_agent_info_items(ev[1], ev[2])
			elif ev[0] == 'AGENT_INFO_INFO':
				self.handle_event_agent_info_info(ev[1], ev[2])
			elif ev[0] == 'REG_AGENT_INFO':
				self.handle_event_reg_agent_info(ev[1], ev[2])
			elif ev[0] == 'ACC_OK':
				self.handle_event_acc_ok(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
			elif ev[0] == 'QUIT':
				self.handle_event_quit(ev[1], ev[2])
				self.handle_event_myvcard(ev[1], ev[2])
			elif ev[0] == 'OS_INFO':
				self.handle_event_os_info(ev[1], ev[2])
			elif ev[0] == 'VCARD':
				self.handle_event_vcard(ev[1], ev[2])
			elif ev[0] == 'LOG_NB_LINE':
				self.handle_event_log_nb_line(ev[1], ev[2])
			elif ev[0] == 'LOG_LINE':
				self.handle_event_log_line(ev[1], ev[2])
			elif ev[0] == 'GC_MSG':
				self.handle_event_gc_msg(ev[1], ev[2])
			elif ev[0] == 'GC_SUBJECT':
				self.handle_event_gc_subject(ev[1], ev[2])
			elif ev[0] == 'BAD_PASSPHRASE':
				self.handle_event_bad_passphrase(ev[1], ev[2])
			elif ev[0] == 'GPG_SECRETE_KEYS':
				self.handle_event_gpg_secrete_keys(ev[1], ev[2])
			elif ev[0] == 'ROSTER_INFO':
				self.handle_event_roster_info(ev[1], ev[2])
Yann Leboulanger's avatar
Yann Leboulanger committed
		return 1
Yann Leboulanger's avatar
Yann Leboulanger committed
	
	def read_sleepy(self):	
		"""Check if we are idle"""
		if not self.sleeper.poll():
			return 1
		state = self.sleeper.getState()
		for account in self.accounts.keys():
			if not self.sleeper_state[account]:
				continue
			if state == common.sleepy.STATE_AWAKE and \
				self.sleeper_state[account] > 1:
				#we go online
				self.send('STATUS', account, ('online', 'Online'))
				self.sleeper_state[account] = 1
			elif state == common.sleepy.STATE_AWAY and \
				self.sleeper_state[account] == 1 and \
				self.config['autoaway']:
				#we go away
				self.send('STATUS', account, ('away', 'auto away (idle)'))
				self.sleeper_state[account] = 2
			elif state == common.sleepy.STATE_XAWAY and (\
				self.sleeper_state[account] == 2 or \
				self.sleeper_state[account] == 1) and \
				self.config['autoxa']:
				#we go extended away
				self.send('STATUS', account, ('xa', 'auto away (idle)'))
				self.sleeper_state[account] = 3
Yann Leboulanger's avatar
Yann Leboulanger committed
		return 1
	def autoconnect(self):
		"""auto connect at startup"""
		for a in self.accounts.keys():
			if self.accounts[a].has_key('autoconnect'):
				if self.accounts[a]['autoconnect']:
					ask_message = 1
					break
		if ask_message:
			message = self.roster.get_status_message('online', 1)
			if message == -1:
				return
			for a in self.accounts.keys():
				if self.accounts[a].has_key('autoconnect'):
					if self.accounts[a]['autoconnect']:
						self.roster.send_status(a, 'online', message, 1)
	def show_systray(self):
		self.systray.show_icon()
nkour's avatar
nkour committed
		self.systray_enabled = True

	def hide_systray(self):
		self.systray.hide_icon()
nkour's avatar
nkour committed
		self.systray_enabled = False
nkour's avatar
nkour committed
	
	def image_is_ok(self, image):
		if not os.path.exists(image):
			return False
		img = gtk.Image()
		try:
			img.set_from_file(image)
		except:
			return True
		if img.get_storage_type() == gtk.IMAGE_PIXBUF:
			pix = img.get_pixbuf()
		else:
			return False
		if pix.get_width() > 24 or pix.get_height() > 24:
			return False
		return True
		# regexp meta characters are:  . ^ $ * + ? { } [ ] \ | ( )
		# one escapes the metachars with \
		# \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
		# \s matches any whitespace character
		# \w any alphanumeric character
		# \W any non-alphanumeric character
		# \b means word boundary. This is a zero-width assertion that
		# 					matches only at the beginning or end of a word.
		# ^ matches at the beginning of lines
		# * means 0 or more times
		# + means 1 or more times
		# ? means 0 or 1 time
		# | means or
		# [^*] anything but '*'   (inside [] you don't have to escape metachars)
		# [^\s*] anything but whitespaces and '*'
nkour's avatar
nkour committed
		# (?<!\S) is a one char lookbehind assertion and asks for any leading whitespace
		# and mathces beginning of lines so we have correct formatting detection
		# even if the the text is just '*foo*'
		# (?!\S) is the same thing but it's a lookahead assertion
		# \S*[^\s)?!,.;] --> in the matching string don't match ? or ) etc.. if at the end
		# so http://be) will match http://be and http://be)be) will match http://be)be
		links = r'\bhttp://\S*[^\s)?!,.;]|' r'\bhttps://\S*[^\s)?!,.;]|' r'\bnews://\S*[^\s)?!,.;]|' r'\bftp://\S*[^\s)?!,.;]|' r'\bed2k://\S*[^\s)?!,.;]|' r'\bwww\.\S*[^\s)?!,.;]|' r'\bftp\.\S*[^\s)?!,.;]|'
		#2nd one: at_least_one_char@at_least_one_char.at_least_one_char
		mail = r'\bmailto:\S*[^\s)?!,.;]|' r'\b\S+@\S+\.\S*[^\s)?]|'
nkour's avatar
nkour committed
		#detects eg. *b* *bold* *bold bold* test *bold*
		#doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
		formatting = r'(?<!\S)\*[^\s*]([^*]*[^\s*])?\*(?!\S)|' r'(?<!\S)/[^\s/]([^/]*[^\s/])?/(?!\S)|' r'(?<!\S)_[^\s_]([^_]*[^\s_])?_(?!\S)'
nkour's avatar
nkour committed

		basic_pattern = links + mail + formatting
		self.basic_pattern_re = sre.compile(basic_pattern, sre.IGNORECASE)
		
		emoticons_pattern = ''
		for emoticon in self.emoticons: # travel thru emoticons list
			emoticon_escaped = sre.escape(emoticon) # espace regexp metachars
			emoticons_pattern += emoticon_escaped + '|'# | means or in regexp

		emot_and_basic_pattern = emoticons_pattern + basic_pattern
		self.emot_and_basic_re = sre.compile(emot_and_basic_pattern,\
															sre.IGNORECASE)
		
		# at least one character in 3 parts (before @, after @, after .)
		self.sth_at_sth_dot_sth_re = sre.compile(r'\S+@\S+\.\S*[^\s)?]')
Yann Leboulanger's avatar
Yann Leboulanger committed
	def on_launch_browser_mailer(self, widget, url, kind):
		self.launch_browser_mailer(kind, url)
		#initialize emoticons dictionary
		self.emoticons = dict()
		split_line = self.config['emoticons'].split('\t')
		for i in range(0, len(split_line)/2):
			emot_file = split_line[2*i+1]
			if not self.image_is_ok(emot_file):
				continue
			pix = gtk.gdk.pixbuf_new_from_file(emot_file)
			self.emoticons[split_line[2*i]] = pix
		
		# update regular expressions
Yann Leboulanger's avatar
Yann Leboulanger committed
	def __init__(self, quIN, quOUT):
		gtk.gdk.threads_init()
		if gtk.pygtk_version >= (2, 6, 0):
			gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
			gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
Yann Leboulanger's avatar
Yann Leboulanger committed
		self.queueIN = quIN
Yann Leboulanger's avatar
Yann Leboulanger committed
		self.queueOUT = quOUT
		self.send('REG_MESSAGE', 'gtkgui', ['ROSTER', 'WARNING', 'ERROR', \
			'STATUS', 'NOTIFY', 'MSG', 'MSGERROR', 'SUBSCRIBED', 'UNSUBSCRIBED', \
			'SUBSCRIBE', 'AGENTS', 'AGENT_INFO', 'AGENT_INFO_ITEMS', \
			'AGENT_INFO_INFO', 'REG_AGENT_INFO', 'QUIT', 'ACC_OK', 'CONFIG', \
			'MYVCARD', 'OS_INFO', 'VCARD', 'LOG_NB_LINE', 'LOG_LINE', 'VISUAL', \
			'GC_MSG', 'GC_SUBJECT', 'BAD_PASSPHRASE', 'GPG_SECRETE_KEYS', \
			'ROSTER_INFO', 'MSGSENT'])
		self.default_config = {'autopopup':0,\
			'autopopupaway':0,\
			'showoffline':0,\
			'autoawaytime':10,\
			'autoxatime':20,\
			'msg0_name':'Nap',\
			'msg0':'I\'m taking a nap.',\
nkour's avatar
nkour committed
			'msg1_name':'Brb',\
			'msg1':'Back in some minutes.',\
			'msg2_name':'Eating',\
			'msg2':'I\'m eating, so leave me a message.',\
			'msg3_name':'Movie',\
			'msg3':'I\'m watching a movie.',\
			'msg4_name':'Working',\
			'msg4':'I\'m working.',\
			'trayicon':1,\
nkour's avatar
nkour committed
			'iconset':'sun',\
			'outmsgcolor': '#0000ff',\
			'statusmsgcolor':'#1eaa1e',\
			'accounttextcolor': '#ffffff',\
			#ff0000
			'accountbgcolor': '#94aa8c',\
			#9fdfff
			'accountfont': 'Sans Bold 10',\
			'grouptextcolor': '#0000ff',\
nkour's avatar
nkour committed
			'groupbgcolor': '#eff3e7',\
			'groupfont': 'Sans Italic 10',\
			'usertextcolor': '#000000',\
			'userbgcolor': '#ffffff',\
			'usetabbedchat': 1,\
Yann Leboulanger's avatar
Yann Leboulanger committed
			'useemoticons': 1,\
			'emoticons': ':-)\tplugins/gtkgui/emoticons/smile.png\t(@)\tplugins/gtkgui/emoticons/pussy.png\t8)\tplugins/gtkgui/emoticons/coolglasses.png\t:(\tplugins/gtkgui/emoticons/unhappy.png\t:)\tplugins/gtkgui/emoticons/smile.png\t(})\tplugins/gtkgui/emoticons/hugleft.png\t:$\tplugins/gtkgui/emoticons/blush.png\t(Y)\tplugins/gtkgui/emoticons/yes.png\t:-@\tplugins/gtkgui/emoticons/angry.png\t:-D\tplugins/gtkgui/emoticons/biggrin.png\t(U)\tplugins/gtkgui/emoticons/brheart.png\t(F)\tplugins/gtkgui/emoticons/flower.png\t:-[\tplugins/gtkgui/emoticons/bat.png\t:>\tplugins/gtkgui/emoticons/biggrin.png\t(T)\tplugins/gtkgui/emoticons/phone.png\t:-S\tplugins/gtkgui/emoticons/frowing.png\t:-P\tplugins/gtkgui/emoticons/tongue.png\t(H)\tplugins/gtkgui/emoticons/coolglasses.png\t(D)\tplugins/gtkgui/emoticons/drink.png\t:-O\tplugins/gtkgui/emoticons/oh.png\t(C)\tplugins/gtkgui/emoticons/coffee.png\t({)\tplugins/gtkgui/emoticons/hugright.png\t(*)\tplugins/gtkgui/emoticons/star.png\tB-)\tplugins/gtkgui/emoticons/coolglasses.png\t(Z)\tplugins/gtkgui/emoticons/boy.png\t(E)\tplugins/gtkgui/emoticons/mail.png\t(N)\tplugins/gtkgui/emoticons/no.png\t(P)\tplugins/gtkgui/emoticons/photo.png\t(K)\tplugins/gtkgui/emoticons/kiss.png\t(R)\tplugins/gtkgui/emoticons/rainbow.png\t:-|\tplugins/gtkgui/emoticons/stare.png\t;-)\tplugins/gtkgui/emoticons/wink.png\t;-(\tplugins/gtkgui/emoticons/cry.png\t(6)\tplugins/gtkgui/emoticons/devil.png\t(L)\tplugins/gtkgui/emoticons/heart.png\t(W)\tplugins/gtkgui/emoticons/brflower.png\t:|\tplugins/gtkgui/emoticons/stare.png\t:O\tplugins/gtkgui/emoticons/oh.png\t;)\tplugins/gtkgui/emoticons/wink.png\t;(\tplugins/gtkgui/emoticons/cry.png\t:S\tplugins/gtkgui/emoticons/frowing.png\t;\'-(\tplugins/gtkgui/emoticons/cry.png\t:-(\tplugins/gtkgui/emoticons/unhappy.png\t8-)\tplugins/gtkgui/emoticons/coolglasses.png\t(B)\tplugins/gtkgui/emoticons/beer.png\t:D\tplugins/gtkgui/emoticons/biggrin.png\t(8)\tplugins/gtkgui/emoticons/music.png\t:@\tplugins/gtkgui/emoticons/angry.png\tB)\tplugins/gtkgui/emoticons/coolglasses.png\t:-$\tplugins/gtkgui/emoticons/blush.png\t:\'(\tplugins/gtkgui/emoticons/cry.png\t:->\tplugins/gtkgui/emoticons/biggrin.png\t:[\tplugins/gtkgui/emoticons/bat.png\t(I)\tplugins/gtkgui/emoticons/lamp.png\t:P\tplugins/gtkgui/emoticons/tongue.png\t(%)\tplugins/gtkgui/emoticons/cuffs.png\t(S)\tplugins/gtkgui/emoticons/moon.png',\
nkour's avatar
nkour committed
			'sounds_on': 1,\
Yann Leboulanger's avatar
Yann Leboulanger committed
			'sound_first_message_received': 1,\
			'sound_first_message_received_file': 'sounds/message1.wav',\
			'sound_next_message_received': 0,\
			'sound_next_message_received_file': 'sounds/message2.wav',\
			'sound_contact_connected': 1,\
			'sound_contact_connected_file': 'sounds/connected.wav',\
			'sound_contact_disconnected': 1,\
			'sound_contact_disconnected_file': 'sounds/disconnected.wav',\
			'sound_message_sent': 1,\
			'sound_message_sent_file': 'sounds/sent.wav',\
			'openwith': 'gnome-open',\
			'custombrowser' : 'firefox',\
			'custommailapp' : 'mozilla-thunderbird -compose',\
			'x-position': 0,\
			'y-position': 0,\
			'width': 150,\
nkour's avatar
nkour committed
			'latest_disco_addresses': '',\
nkour's avatar
nkour committed
			'before_time': '[',\
			'after_time': ']',\
			'before_nickname': '<',\
			'after_nickname': '>',\
nkour's avatar
nkour committed
			}
bigpod's avatar
bigpod committed
		self.send('ASK_CONFIG', None, ('GtkGui', 'GtkGui', self.default_config))
		self.config = self.wait('CONFIG')
Yann Leboulanger's avatar
Yann Leboulanger committed
		self.send('ASK_CONFIG', None, ('GtkGui', 'accounts'))
		self.accounts = self.wait('CONFIG')
		self.windows = {'logs':{}}
		self.queues = {}
Yann Leboulanger's avatar
Yann Leboulanger committed
		self.connected = {}
		self.sleeper_state = {} #whether we pass auto away / xa or not
		for a in self.accounts.keys():
			self.windows[a] = {'infos': {}, 'chats': {}, 'gc': {}}
			self.queues[a] = {}
			self.connected[a] = 0 #0->offline 1->connecting 2->online 3->away
										#4->xa 5->dnd 6->invisible
			self.sleeper_state[a] = 0	#0:don't use sleeper for this account
												#1:online and use sleeper
												#2:autoaway and use sleeper
												#3:autoxa and use sleeper
			self.send('ASK_ROSTER', a, self.queueIN)
nkour's avatar
nkour committed
		iconset = self.config['iconset']
		if not iconset:
			iconset = 'sun'
		path = 'plugins/gtkgui/iconsets/' + iconset + '/'
		files = [path + 'online.gif', path + 'online.png', path + 'online.xpm']
Yann Leboulanger's avatar
Yann Leboulanger committed
		pix = None
		for fname in files:
			if os.path.exists(fname):
				pix = gtk.gdk.pixbuf_new_from_file(fname)
Yann Leboulanger's avatar
Yann Leboulanger committed
		if pix:
			gtk.window_set_default_icon(pix)
nkour's avatar
nkour committed
		self.roster = Roster_window(self)
nkour's avatar
nkour committed
		gobject.timeout_add(100, self.read_queue)
		gobject.timeout_add(100, self.read_sleepy)
		self.sleeper = common.sleepy.Sleepy( \
			self.config['autoawaytime']*60, \
			self.config['autoxatime']*60)
nkour's avatar
nkour committed
		self.systray_enabled = False
nkour's avatar
nkour committed
			import egg.trayicon as trayicon # use gnomepythonextras trayicon
			try:
				import trayicon # use yann's
			except: # user doesn't have trayicon capabilities
				self.config['trayicon'] = 0
				self.send('CONFIG', None, ('GtkGui', self.config, 'GtkGui'))
				self.systray_capabilities = False
Yann Leboulanger's avatar
Yann Leboulanger committed
			else:
nkour's avatar
nkour committed
				self.systray = Systray(self)
			self.systray_capabilities = True
nkour's avatar
nkour committed
			self.systray = Systray(self)
		if self.config['trayicon']:
			self.show_systray()
nkour's avatar
nkour committed
			
nkour's avatar
nkour committed
		
		# get instances for windows/dialogs that will show_all()/hide()
		self.windows['preferences'] = Preferences_window(self)
nkour's avatar
nkour committed
		self.windows['add_remove_emoticons_window'] = \
														Add_remove_emoticons_window(self)
		self.windows['roster'] = self.roster