4 ## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
6 ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
7 ## Norman Rasmussen <norman AT rasmussen.co.za>
8 ## Stéphan Kochen <stephan AT kochen.nl>
9 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
10 ## Alex Mauer <hawke AT hawkesnest.net>
11 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
12 ## Nikos Kouremenos <kourem AT gmail.com>
13 ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
14 ## Stefan Bethge <stefan AT lanpartei.de>
15 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
16 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
17 ## James Newton <redshodan AT gmail.com>
18 ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
19 ## Julien Pivotto <roidelapluie AT gmail.com>
20 ## Stephan Erb <steve-e AT h3c.de>
21 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
23 ## This file is part of Gajim.
25 ## Gajim is free software; you can redistribute it and/or modify
26 ## it under the terms of the GNU General Public License as published
27 ## by the Free Software Foundation; version 3 only.
29 ## Gajim is distributed in the hope that it will be useful,
30 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
31 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 ## GNU General Public License for more details.
34 ## You should have received a copy of the GNU General Public License
35 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
43 warnings
.filterwarnings(action
='ignore')
45 if os
.path
.isdir('gtk'):
46 # Used to create windows installer with GTK included
47 paths
= os
.environ
['PATH']
48 list_
= paths
.split(';')
51 if p
.find('gtk') < 0 and p
.find('GTK') < 0:
53 new_list
.insert(0, 'gtk/lib')
54 new_list
.insert(0, 'gtk/bin')
55 os
.environ
['PATH'] = ';'.join(new_list
)
56 os
.environ
['GTK_BASEPATH'] = 'gtk'
62 from common
import logging_helpers
63 logging_helpers
.init('TERM' in os
.environ
)
66 # gajim.gui or gajim.gtk more appropriate ?
67 log
= logging
.getLogger('gajim.gajim')
70 from common
import i18n
77 shortargs
= 'hqvl:p:c:'
78 longargs
= 'help quiet verbose loglevel= profile= config_path='
79 opts
= getopt
.getopt(sys
.argv
[1:], shortargs
, longargs
.split())[0]
80 except getopt
.error
, msg
:
82 print 'for help use --help'
85 if o
in ('-h', '--help'):
86 print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]'
88 elif o
in ('-q', '--quiet'):
89 logging_helpers
.set_quiet()
90 elif o
in ('-v', '--verbose'):
91 logging_helpers
.set_verbose()
92 elif o
in ('-p', '--profile'): # gajim --profile name
94 elif o
in ('-l', '--loglevel'):
95 logging_helpers
.set_loglevels(a
)
96 elif o
in ('-c', '--config-path'):
98 return profile
, config_path
100 profile
, config_path
= parseOpts()
104 profile
= unicode(profile
, locale
.getpreferredencoding())
106 import common
.configpaths
107 common
.configpaths
.gajimpaths
.init(config_path
)
109 common
.configpaths
.gajimpaths
.init_profile(profile
)
113 class MyStderr(object):
116 def write(self
, text
):
117 fname
=os
.path
.join(common
.configpaths
.gajimpaths
.root
,
118 os
.path
.split(sys
.executable
)[1]+'.log')
119 if self
._file
is None and self
._error
is None:
121 self
._file
= open(fname
, 'a')
122 except Exception, details
:
123 self
._error
= details
124 if self
._file
is not None:
125 self
._file
.write(text
)
128 if self
._file
is not None:
131 sys
.stderr
= MyStderr()
133 # PyGTK2.10+ only throws a warning
134 warnings
.filterwarnings('error', module
='gtk')
138 if str(msg
) == 'could not open display':
139 print >> sys
.stderr
, _('Gajim needs X server to run. Quiting...')
141 print >> sys
.stderr
, _('importing PyGTK failed: %s') % str(msg
)
143 warnings
.resetwarnings()
146 warnings
.filterwarnings(action
='ignore')
150 from common
import exceptions
152 from common
import gajim
153 except exceptions
.DatabaseMalformed
:
154 pritext
= _('Database Error')
155 sectext
= _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common
.logger
.LOG_DB_PATH
157 from common
import dbus_support
158 if dbus_support
.supported
:
161 if os
.name
== 'posix': # dl module is Unix Only
162 try: # rename the process name to gajim
164 libc
= dl
.open('/lib/libc.so.6')
165 libc
.call('prctl', 15, 'gajim\0', 0, 0, 0)
169 if gtk
.pygtk_version
< (2, 12, 0):
170 pritext
= _('Gajim needs PyGTK 2.12 or above')
171 sectext
= _('Gajim needs PyGTK 2.12 or above to run. Quiting...')
172 elif gtk
.gtk_version
< (2, 12, 0):
173 pritext
= _('Gajim needs GTK 2.12 or above')
174 sectext
= _('Gajim needs GTK 2.12 or above to run. Quiting...')
177 import gtk
.glade
# check if user has libglade (in pygtk and in gtk)
179 pritext
= _('GTK+ runtime is missing libglade support')
181 sectext
= _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net'
183 sectext
= _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
186 from common
import check_paths
187 except exceptions
.PysqliteNotAvailable
, e
:
188 pritext
= _('Gajim needs PySQLite2 to run')
193 import winsound
# windows-only built-in module for playing wav
194 import win32api
# do NOT remove. we req this module
196 pritext
= _('Gajim needs pywin32 to run')
197 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'
200 dlg
= gtk
.MessageDialog(None,
201 gtk
.DIALOG_DESTROY_WITH_PARENT | gtk
.DIALOG_MODAL
,
202 gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
, message_format
= pritext
)
204 dlg
.format_secondary_text(sectext
)
214 if not hasattr(gobject
, 'timeout_add_seconds'):
215 def timeout_add_seconds_fake(time_sec
, *args
):
216 return gobject
.timeout_add(time_sec
* 1000, *args
)
217 gobject
.timeout_add_seconds
= timeout_add_seconds_fake
224 import gtkgui_helpers
226 import message_control
228 from chat_control
import ChatControlBase
229 from chat_control
import ChatControl
230 from groupchat_control
import GroupchatControl
231 from groupchat_control
import PrivateChatControl
232 from atom_window
import AtomWindow
233 from session
import ChatControlSession
237 from common
.xmpp
import idlequeue
238 from common
.zeroconf
import connection_zeroconf
239 from common
import resolver
240 from common
import proxy65_manager
241 from common
import socks5
242 from common
import helpers
243 from common
import optparser
244 from common
import dataforms
245 from common
import passwords
247 gajimpaths
= common
.configpaths
.gajimpaths
249 pid_filename
= gajimpaths
['PID_FILE']
250 config_filename
= gajimpaths
['CONFIG_FILE']
258 pf
= open(pid_filename
)
260 # probably file not found
264 pid
= int(pf
.read().strip())
267 traceback
.print_exc()
268 # PID file exists, but something happened trying to read PID
269 # Could be 0.10 style empty PID file, so assume Gajim is running
274 from ctypes
import (windll
, c_ulong
, c_int
, Structure
, c_char
, POINTER
, pointer
, )
278 class PROCESSENTRY32(Structure
):
280 ('dwSize', c_ulong
, ),
281 ('cntUsage', c_ulong
, ),
282 ('th32ProcessID', c_ulong
, ),
283 ('th32DefaultHeapID', c_ulong
, ),
284 ('th32ModuleID', c_ulong
, ),
285 ('cntThreads', c_ulong
, ),
286 ('th32ParentProcessID', c_ulong
, ),
287 ('pcPriClassBase', c_ulong
, ),
288 ('dwFlags', c_ulong
, ),
289 ('szExeFile', c_char
*512, ),
292 Structure
.__init
__(self
, 512+9*4)
295 k
.CreateToolhelp32Snapshot
.argtypes
= c_ulong
, c_ulong
,
296 k
.CreateToolhelp32Snapshot
.restype
= c_int
297 k
.Process32First
.argtypes
= c_int
, POINTER(PROCESSENTRY32
),
298 k
.Process32First
.restype
= c_int
299 k
.Process32Next
.argtypes
= c_int
, POINTER(PROCESSENTRY32
),
300 k
.Process32Next
.restype
= c_int
303 h
= k
.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
304 assert h
> 0, 'CreateToolhelp32Snapshot failed'
305 b
= pointer(PROCESSENTRY32())
306 f
= k
.Process32First(h
, b
)
308 if b
.contents
.th32ProcessID
== p
:
309 return b
.contents
.szExeFile
310 f
= k
.Process32Next(h
, b
)
312 if get_p(pid
) in ('python.exe', 'gajim.exe'):
316 if not os
.path
.exists('/proc'):
317 return True # no /proc, assume Gajim is running
320 f
= open('/proc/%d/cmdline'% pid
)
322 if e
.errno
== errno
.ENOENT
:
323 return False # file/pid does not exist
328 if n
.find('gajim') < 0:
330 return True # Running Gajim found at pid
332 traceback
.print_exc()
334 # If we are here, pidfile exists, but some unexpected error occured.
335 # Assume Gajim is running.
339 path_to_file
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps/gajim.png')
340 pix
= gtk
.gdk
.pixbuf_new_from_file(path_to_file
)
341 gtk
.window_set_default_icon(pix
) # set the icon to all newly opened wind
342 pritext
= _('Gajim is already running')
343 sectext
= _('Another instance of Gajim seems to be running\nRun anyway?')
344 dialog
= dialogs
.YesNoDialog(pritext
, sectext
)
346 if dialog
.run() != gtk
.RESPONSE_YES
:
349 # run anyway, delete pid and useless global vars
350 if os
.path
.exists(pid_filename
):
351 os
.remove(pid_filename
)
359 pid_dir
= os
.path
.dirname(pid_filename
)
360 if not os
.path
.exists(pid_dir
):
361 check_paths
.create_path(pid_dir
)
364 f
= open(pid_filename
, 'w')
365 f
.write(str(os
.getpid()))
368 dlg
= dialogs
.ErrorDialog(_('Disk Write Error'), str(e
))
376 # delete pid file on normal exit
377 if os
.path
.exists(pid_filename
):
378 os
.remove(pid_filename
)
379 # Shutdown GUI and save config
380 gajim
.interface
.roster
.prepare_quit()
383 atexit
.register(on_exit
)
385 parser
= optparser
.OptionsParser(config_filename
)
388 import profile_window
390 from threading
import Thread
393 class PassphraseRequest
:
394 def __init__(self
, keyid
):
397 self
.dialog_created
= False
399 self
.completed
= False
402 self
.dialog
.window
.destroy()
405 def run_callback(self
, account
, callback
):
406 gajim
.connections
[account
].gpg_passphrase(self
.passphrase
)
409 def add_callback(self
, account
, cb
):
411 self
.run_callback(account
, cb
)
413 self
.callbacks
.append((account
, cb
))
414 if not self
.dialog_created
:
415 self
.create_dialog(account
)
417 def complete(self
, passphrase
):
418 self
.passphrase
= passphrase
419 self
.completed
= True
420 if passphrase
is not None:
421 gobject
.timeout_add_seconds(30, gajim
.interface
.forget_gpg_passphrase
,
423 for (account
, cb
) in self
.callbacks
:
424 self
.run_callback(account
, cb
)
427 def create_dialog(self
, account
):
428 title
= _('Passphrase Required')
429 second
= _('Enter GPG key passphrase for key %(keyid)s (account '
430 '%(account)s).') % {'keyid': self
.keyid
, 'account': account
}
433 # user cancelled, continue without GPG
436 def _ok(passphrase
, checked
, count
):
437 result
= gajim
.connections
[account
].test_gpg_passphrase(passphrase
)
440 self
.complete(passphrase
)
442 elif result
== 'expired':
443 dialogs
.ErrorDialog(_('GPG key expired'),
444 _('Your GPG key has expied, you will be connected to %s without '
445 'OpenPGP.') % account
)
446 # Don't try to connect with GPG
447 gajim
.connections
[account
].continue_connect_info
[2] = False
453 dialogs
.PassphraseDialog(_('Wrong Passphrase'),
454 _('Please retype your GPG passphrase or press Cancel.'),
455 ok_handler
=(_ok
, count
+ 1), cancel_handler
=_cancel
)
457 # user failed 3 times, continue without GPG
460 self
.dialog
= dialogs
.PassphraseDialog(title
, second
, ok_handler
=(_ok
, 1),
461 cancel_handler
=_cancel
)
462 self
.dialog_created
= True
465 class ThreadInterface
:
466 def __init__(self
, func
, func_args
, callback
, callback_args
):
467 '''Call a function in a thread
469 :param func: the function to call in the thread
470 :param func_args: list or arguments for this function
471 :param callback: callback to call once function is finished
472 :param callback_args: list of arguments for this callback
474 def thread_function(func
, func_args
, callback
, callback_args
):
475 output
= func(*func_args
)
476 gobject
.idle_add(callback
, output
, *callback_args
)
477 Thread(target
=thread_function
, args
=(func
, func_args
, callback
,
478 callback_args
)).start()
482 ################################################################################
483 ### Methods handling events from connection
484 ################################################################################
486 def handle_event_roster(self
, account
, data
):
487 #('ROSTER', account, array)
488 # FIXME: Those methods depend to highly on each other
489 # and the order in which they are called
490 self
.roster
.fill_contacts_and_groups_dicts(data
, account
)
491 self
.roster
.add_account_contacts(account
)
492 self
.roster
.fire_up_unread_messages_events(account
)
494 self
.remote_ctrl
.raise_signal('Roster', (account
, data
))
496 def handle_event_warning(self
, unused
, data
):
497 #('WARNING', account, (title_text, section_text))
498 dialogs
.WarningDialog(data
[0], data
[1])
500 def handle_event_error(self
, unused
, data
):
501 #('ERROR', account, (title_text, section_text))
502 dialogs
.ErrorDialog(data
[0], data
[1])
504 def handle_event_information(self
, unused
, data
):
505 #('INFORMATION', account, (title_text, section_text))
506 dialogs
.InformationDialog(data
[0], data
[1])
508 def handle_event_ask_new_nick(self
, account
, data
):
509 #('ASK_NEW_NICK', account, (room_jid,))
511 title
= _('Unable to join group chat')
512 prompt
= _('Your desired nickname in group chat %s is in use or '
513 'registered by another occupant.\nPlease specify another nickname '
515 check_text
= _('Always use this nickname when there is a conflict')
516 if 'change_nick_dialog' in self
.instances
:
517 self
.instances
['change_nick_dialog'].add_room(account
, room_jid
,
520 self
.instances
['change_nick_dialog'] = dialogs
.ChangeNickDialog(
521 account
, room_jid
, title
, prompt
)
523 def handle_event_http_auth(self
, account
, data
):
524 #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
525 def response(account
, iq_obj
, answer
):
526 self
.dialog
.destroy()
527 gajim
.connections
[account
].build_http_auth_answer(iq_obj
, answer
)
529 def on_yes(is_checked
, account
, iq_obj
):
530 response(account
, iq_obj
, 'yes')
532 sec_msg
= _('Do you accept this request?')
533 if gajim
.get_number_of_connected_accounts() > 1:
534 sec_msg
= _('Do you accept this request on account %s?') % account
536 sec_msg
= data
[4] + '\n' + sec_msg
537 self
.dialog
= dialogs
.YesNoDialog(_('HTTP (%(method)s) Authorization for '
538 '%(url)s (id: %(id)s)') % {'method': data
[0], 'url': data
[1],
539 'id': data
[2]}, sec_msg
, on_response_yes
=(on_yes
, account
, data
[3]),
540 on_response_no
=(response
, account
, data
[3], 'no'))
542 def handle_event_error_answer(self
, account
, array
):
543 #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
544 id_
, jid_from
, errmsg
, errcode
= array
545 if unicode(errcode
) in ('400', '403', '406') and id_
:
546 # show the error dialog
547 ft
= self
.instances
['file_transfers']
549 if len(id_
) > 3 and id_
[2] == '_':
551 if sid
in ft
.files_props
['s']:
552 file_props
= ft
.files_props
['s'][sid
]
553 if unicode(errcode
) == '400':
554 file_props
['error'] = -3
556 file_props
['error'] = -4
557 self
.handle_event_file_request_error(account
,
558 (jid_from
, file_props
, errmsg
))
559 conn
= gajim
.connections
[account
]
560 conn
.disconnect_transfer(file_props
)
562 elif unicode(errcode
) == '404':
563 conn
= gajim
.connections
[account
]
565 if len(id_
) > 3 and id_
[2] == '_':
567 if sid
in conn
.files_props
:
568 file_props
= conn
.files_props
[sid
]
569 self
.handle_event_file_send_error(account
,
570 (jid_from
, file_props
))
571 conn
.disconnect_transfer(file_props
)
574 ctrl
= self
.msg_win_mgr
.get_control(jid_from
, account
)
575 if ctrl
and ctrl
.type_id
== message_control
.TYPE_GC
:
576 ctrl
.print_conversation('Error %s: %s' % (array
[2], array
[1]))
578 def handle_event_con_type(self
, account
, con_type
):
579 # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
580 gajim
.con_types
[account
] = con_type
581 self
.roster
.draw_account(account
)
583 def handle_event_connection_lost(self
, account
, array
):
584 # ('CONNECTION_LOST', account, [title, text])
585 path
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events',
586 'connection_lost.png')
587 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(path
)
588 notify
.popup(_('Connection Failed'), account
, account
,
589 'connection_failed', path
, array
[0], array
[1])
591 def unblock_signed_in_notifications(self
, account
):
592 gajim
.block_signed_in_notifications
[account
] = False
594 def handle_event_status(self
, account
, status
): # OUR status
595 #('STATUS', account, status)
596 model
= self
.roster
.status_combobox
.get_model()
597 if status
in ('offline', 'error'):
598 for name
in self
.instances
[account
]['online_dialog'].keys():
599 # .keys() is needed to not have a dictionary length changed during
601 self
.instances
[account
]['online_dialog'][name
].destroy()
602 del self
.instances
[account
]['online_dialog'][name
]
603 for request
in self
.gpg_passphrase
.values():
606 # .keys() is needed because dict changes during loop
607 for account
in self
.pass_dialog
.keys():
608 self
.pass_dialog
[account
].window
.destroy()
609 if status
== 'offline':
610 # sensitivity for this menuitem
611 if gajim
.get_number_of_connected_accounts() == 0:
612 model
[self
.roster
.status_message_menuitem_iter
][3] = False
613 gajim
.block_signed_in_notifications
[account
] = True
615 # 30 seconds after we change our status to sth else than offline
616 # we stop blocking notifications of any kind
617 # this prevents from getting the roster items as 'just signed in'
618 # contacts. 30 seconds should be enough time
619 gobject
.timeout_add_seconds(30, self
.unblock_signed_in_notifications
, account
)
620 # sensitivity for this menuitem
621 model
[self
.roster
.status_message_menuitem_iter
][3] = True
623 # Inform all controls for this account of the connection state change
624 ctrls
= self
.msg_win_mgr
.get_controls()
625 if account
in self
.minimized_controls
:
626 # Can not be the case when we remove account
627 ctrls
+= self
.minimized_controls
[account
].values()
629 if ctrl
.account
== account
:
630 if status
== 'offline' or (status
== 'invisible' and \
631 gajim
.connections
[account
].is_zeroconf
):
632 ctrl
.got_disconnected()
634 # Other code rejoins all GCs, so we don't do it here
635 if not ctrl
.type_id
== message_control
.TYPE_GC
:
638 ctrl
.parent_win
.redraw_tab(ctrl
)
640 self
.roster
.on_status_changed(account
, status
)
641 if account
in self
.show_vcard_when_connect
and status
not in ('offline',
643 self
.edit_own_details(account
)
645 self
.remote_ctrl
.raise_signal('AccountPresence', (status
, account
))
647 def edit_own_details(self
, account
):
648 jid
= gajim
.get_jid_from_account(account
)
649 if 'profile' not in self
.instances
[account
]:
650 self
.instances
[account
]['profile'] = \
651 profile_window
.ProfileWindow(account
)
652 gajim
.connections
[account
].request_vcard(jid
)
654 def handle_event_notify(self
, account
, array
):
655 # 'NOTIFY' (account, (jid, status, status message, resource,
656 # priority, # keyID, timestamp, contact_nickname))
658 # Contact changed show
660 # FIXME: Drop and rewrite...
662 statuss
= ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
664 # Ignore invalid show
665 if array
[1] not in statuss
:
668 new_show
= statuss
.index(array
[1])
669 status_message
= array
[2]
670 jid
= array
[0].split('/')[0]
672 contact_nickname
= array
[7]
674 # Get the proper keyID
675 keyID
= helpers
.prepare_and_validate_gpg_keyID(account
, jid
, keyID
)
681 if gajim
.jid_is_transport(jid
):
682 # It must be an agent
683 ji
= jid
.replace('@', '')
687 highest
= gajim
.contacts
. \
688 get_contact_with_highest_priority(account
, jid
)
689 was_highest
= (highest
and highest
.resource
== resource
)
691 conn
= gajim
.connections
[account
]
694 jid_list
= gajim
.contacts
.get_jid_list(account
)
695 if ji
in jid_list
or jid
== gajim
.get_jid_from_account(account
):
696 lcontact
= gajim
.contacts
.get_contacts(account
, ji
)
700 resources
.append(c
.resource
)
701 if c
.resource
== resource
:
706 if contact1
.show
in statuss
:
707 old_show
= statuss
.index(contact1
.show
)
709 if contact_nickname
is not None and \
710 contact1
.contact_name
!= contact_nickname
:
711 contact1
.contact_name
= contact_nickname
712 self
.roster
.draw_contact(jid
, account
)
714 if old_show
== new_show
and contact1
.status
== status_message
and \
715 contact1
.priority
== priority
: # no change
718 contact1
= gajim
.contacts
.get_first_contact_from_jid(account
, ji
)
720 # Presence of another resource of our
722 # Create self contact and add to roster
723 if resource
== conn
.server_resource
:
725 # Ignore offline presence of unknown self resource
728 contact1
= gajim
.contacts
.create_contact(jid
=ji
,
729 name
=gajim
.nicks
[account
], groups
=['self_contact'],
730 show
=array
[1], status
=status_message
, sub
='both', ask
='none',
731 priority
=priority
, keyID
=keyID
, resource
=resource
,
732 mood
=conn
.mood
, tune
=conn
.tune
, activity
=conn
.activity
)
734 gajim
.contacts
.add_contact(account
, contact1
)
735 lcontact
.append(contact1
)
736 elif contact1
.show
in statuss
:
737 old_show
= statuss
.index(contact1
.show
)
739 if (resources
!= [''] and (len(lcontact
) != 1 or \
740 lcontact
[0].show
!= 'offline')) and jid
.find('@') > 0:
742 contact1
= gajim
.contacts
.copy_contact(contact1
)
743 lcontact
.append(contact1
)
744 contact1
.resource
= resource
746 self
.roster
.add_contact(contact1
.jid
, account
)
748 if contact1
.jid
.find('@') > 0 and len(lcontact
) == 1:
750 if old_show
== 0 and new_show
> 1:
751 if not contact1
.jid
in gajim
.newly_added
[account
]:
752 gajim
.newly_added
[account
].append(contact1
.jid
)
753 if contact1
.jid
in gajim
.to_be_removed
[account
]:
754 gajim
.to_be_removed
[account
].remove(contact1
.jid
)
755 gobject
.timeout_add_seconds(5, self
.roster
.remove_newly_added
,
756 contact1
.jid
, account
)
757 elif old_show
> 1 and new_show
== 0 and conn
.connected
> 1:
758 if not contact1
.jid
in gajim
.to_be_removed
[account
]:
759 gajim
.to_be_removed
[account
].append(contact1
.jid
)
760 if contact1
.jid
in gajim
.newly_added
[account
]:
761 gajim
.newly_added
[account
].remove(contact1
.jid
)
762 self
.roster
.draw_contact(contact1
.jid
, account
)
763 gobject
.timeout_add_seconds(5, self
.roster
.remove_to_be_removed
,
764 contact1
.jid
, account
)
766 # unset custom status
767 if (old_show
== 0 and new_show
> 1) or (old_show
> 1 and new_show
== 0\
768 and conn
.connected
> 1):
769 if account
in self
.status_sent_to_users
and \
770 jid
in self
.status_sent_to_users
[account
]:
771 del self
.status_sent_to_users
[account
][jid
]
773 contact1
.show
= array
[1]
774 contact1
.status
= status_message
775 contact1
.priority
= priority
776 contact1
.keyID
= keyID
779 contact1
.last_status_time
= timestamp
780 elif not gajim
.block_signed_in_notifications
[account
]:
781 # We're connected since more that 30 seconds
782 contact1
.last_status_time
= time
.localtime()
783 contact1
.contact_nickname
= contact_nickname
785 if gajim
.jid_is_transport(jid
):
786 # It must be an agent
788 # Update existing iter and group counting
789 self
.roster
.draw_contact(ji
, account
)
790 self
.roster
.draw_group(_('Transports'), account
)
791 if new_show
> 1 and ji
in gajim
.transport_avatar
[account
]:
792 # transport just signed in.
794 for jid_
in gajim
.transport_avatar
[account
][ji
]:
795 conn
.request_vcard(jid_
)
796 # transport just signed in/out, don't show
797 # popup notifications for 30s
798 account_ji
= account
+ '/' + ji
799 gajim
.block_signed_in_notifications
[account_ji
] = True
800 gobject
.timeout_add_seconds(30,
801 self
.unblock_signed_in_notifications
, account_ji
)
802 locations
= (self
.instances
, self
.instances
[account
])
803 for location
in locations
:
804 if 'add_contact' in location
:
805 if old_show
== 0 and new_show
> 1:
806 location
['add_contact'].transport_signed_in(jid
)
808 elif old_show
> 1 and new_show
== 0:
809 location
['add_contact'].transport_signed_out(jid
)
813 # reset chatstate if needed:
814 # (when contact signs out or has errors)
815 if array
[1] in ('offline', 'error'):
816 contact1
.our_chatstate
= contact1
.chatstate
= \
817 contact1
.composing_xep
= None
819 # TODO: This causes problems when another
820 # resource signs off!
821 conn
.remove_transfers_for_contact(contact1
)
823 # disable encryption, since if any messages are
824 # lost they'll be not decryptable (note that
825 # this contradicts XEP-0201 - trying to get that
826 # in the XEP, though)
828 # there won't be any sessions here if the contact terminated
829 # their sessions before going offline (which we do)
830 for sess
in conn
.get_sessions(ji
):
831 if (ji
+'/'+resource
) != str(sess
.jid
):
834 sess
.control
.no_autonegotiation
= False
835 if sess
.enable_encryption
:
837 conn
.delete_session(jid
, sess
.thread_id
)
839 self
.roster
.chg_contact_status(contact1
, array
[1], status_message
,
842 if old_show
< 2 and new_show
> 1:
843 notify
.notify('contact_connected', jid
, account
, status_message
)
845 self
.remote_ctrl
.raise_signal('ContactPresence', (account
,
848 elif old_show
> 1 and new_show
< 2:
849 notify
.notify('contact_disconnected', jid
, account
, status_message
)
851 self
.remote_ctrl
.raise_signal('ContactAbsence', (account
, array
))
852 # FIXME: stop non active file transfers
853 # Status change (not connected/disconnected or
856 notify
.notify('status_change', jid
, account
, [new_show
,
859 self
.remote_ctrl
.raise_signal('ContactStatus', (account
, array
))
861 # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't
862 # follow the XEP, still the case in 2008.
863 # It's maybe a GC_NOTIFY (specialy for MSN gc)
864 self
.handle_event_gc_notify(account
, (jid
, array
[1], status_message
,
865 array
[3], None, None, None, None, None, [], None, None))
867 highest
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
868 is_highest
= (highest
and highest
.resource
== resource
)
870 # disconnect the session from the ctrl if the highest resource has changed
871 if (was_highest
and not is_highest
) or (not was_highest
and is_highest
):
872 ctrl
= self
.msg_win_mgr
.get_control(jid
, account
)
875 ctrl
.set_session(None)
876 ctrl
.contact
= highest
878 def handle_event_msgerror(self
, account
, array
):
879 #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session]))
880 full_jid_with_resource
= array
[0]
881 jids
= full_jid_with_resource
.split('/', 1)
888 gc_control
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
889 if not gc_control
and \
890 jid
in self
.minimized_controls
[account
]:
891 gc_control
= self
.minimized_controls
[account
][jid
]
892 if gc_control
and gc_control
.type_id
!= message_control
.TYPE_GC
:
895 if len(jids
) > 1: # it's a pm
899 ctrl
= session
.control
901 ctrl
= self
.msg_win_mgr
.get_control(full_jid_with_resource
, account
)
904 tv
= gc_control
.list_treeview
905 model
= tv
.get_model()
906 iter_
= gc_control
.get_contact_iter(nick
)
908 show
= model
[iter_
][3]
911 gc_c
= gajim
.contacts
.create_gc_contact(room_jid
= jid
,
912 name
= nick
, show
= show
)
913 ctrl
= self
.new_private_chat(gc_c
, account
, session
)
915 ctrl
.print_conversation(_('Error %(code)s: %(msg)s') % {
916 'code': array
[1], 'msg': array
[2]}, 'status')
919 gc_control
.print_conversation(_('Error %(code)s: %(msg)s') % {
920 'code': array
[1], 'msg': array
[2]}, 'status')
921 if gc_control
.parent_win
and gc_control
.parent_win
.get_active_jid() == jid
:
922 gc_control
.set_subject(gc_control
.subject
)
925 if gajim
.jid_is_transport(jid
):
926 jid
= jid
.replace('@', '')
929 msg
= _('error while sending %(message)s ( %(error)s )') % {
930 'message': array
[3], 'error': msg
}
932 session
.roster_message(jid
, msg
, array
[4], msg_type
='error')
934 def handle_event_msgsent(self
, account
, array
):
935 #('MSGSENT', account, (jid, msg, keyID))
937 # do not play sound when standalone chatstate message (eg no msg)
938 if msg
and gajim
.config
.get_per('soundevents', 'message_sent', 'enabled'):
939 helpers
.play_sound('message_sent')
941 def handle_event_msgnotsent(self
, account
, array
):
942 #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
943 msg
= _('error while sending %(message)s ( %(error)s )') % {
944 'message': array
[2], 'error': array
[1]}
946 # No session. This can happen when sending a message from gajim-remote
949 array
[4].roster_message(array
[0], msg
, array
[3], account
,
952 def handle_event_subscribe(self
, account
, array
):
953 #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
955 self
.remote_ctrl
.raise_signal('Subscribe', (account
, array
))
960 if helpers
.allow_popup_window(account
) or not self
.systray_enabled
:
961 dialogs
.SubscriptionRequestWindow(jid
, text
, account
, nick
)
964 self
.add_event(account
, jid
, 'subscription_request', (text
, nick
))
966 if helpers
.allow_showing_notification(account
):
967 path
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events',
968 'subscription_request.png')
969 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(path
)
970 event_type
= _('Subscription request')
971 notify
.popup(event_type
, jid
, account
, 'subscription_request', path
,
974 def handle_event_subscribed(self
, account
, array
):
975 #('SUBSCRIBED', account, (jid, resource))
977 if jid
in gajim
.contacts
.get_jid_list(account
):
978 c
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
979 c
.resource
= array
[1]
980 self
.roster
.remove_contact_from_groups(c
.jid
, account
,
981 [_('Not in Roster'), _('Observers')], update
=False)
984 attached_keys
= gajim
.config
.get_per('accounts', account
,
985 'attached_gpg_keys').split()
986 if jid
in attached_keys
:
987 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
988 name
= jid
.split('@', 1)[0]
989 name
= name
.split('%', 1)[0]
990 contact1
= gajim
.contacts
.create_contact(jid
=jid
, name
=name
,
991 groups
=[], show
='online', status
='online',
992 ask
='to', resource
=array
[1], keyID
=keyID
)
993 gajim
.contacts
.add_contact(account
, contact1
)
994 self
.roster
.add_contact(jid
, account
)
995 dialogs
.InformationDialog(_('Authorization accepted'),
996 _('The contact "%s" has authorized you to see his or her status.')
998 if not gajim
.config
.get_per('accounts', account
, 'dont_ack_subscription'):
999 gajim
.connections
[account
].ack_subscribed(jid
)
1000 if self
.remote_ctrl
:
1001 self
.remote_ctrl
.raise_signal('Subscribed', (account
, array
))
1003 def show_unsubscribed_dialog(self
, account
, contact
):
1004 def on_yes(is_checked
, list_
):
1005 self
.roster
.on_req_usub(None, list_
)
1006 list_
= [(contact
, account
)]
1007 dialogs
.YesNoDialog(
1008 _('Contact "%s" removed subscription from you') % contact
.jid
,
1009 _('You will always see him or her as offline.\nDo you want to '
1010 'remove him or her from your contact list?'),
1011 on_response_yes
=(on_yes
, list_
))
1012 # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
1015 def handle_event_unsubscribed(self
, account
, jid
):
1016 #('UNSUBSCRIBED', account, jid)
1017 gajim
.connections
[account
].ack_unsubscribed(jid
)
1018 if self
.remote_ctrl
:
1019 self
.remote_ctrl
.raise_signal('Unsubscribed', (account
, jid
))
1021 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
1025 if helpers
.allow_popup_window(account
) or not self
.systray_enabled
:
1026 self
.show_unsubscribed_dialog(account
, contact
)
1028 self
.add_event(account
, jid
, 'unsubscribed', contact
)
1030 if helpers
.allow_showing_notification(account
):
1031 path
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events',
1033 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(path
)
1034 event_type
= _('Unsubscribed')
1035 notify
.popup(event_type
, jid
, account
, 'unsubscribed', path
,
1038 def handle_event_agent_info_error(self
, account
, agent
):
1039 #('AGENT_ERROR_INFO', account, (agent))
1041 gajim
.connections
[account
].services_cache
.agent_info_error(agent
)
1042 except AttributeError:
1045 def handle_event_agent_items_error(self
, account
, agent
):
1046 #('AGENT_ERROR_INFO', account, (agent))
1048 gajim
.connections
[account
].services_cache
.agent_items_error(agent
)
1049 except AttributeError:
1052 def handle_event_agent_removed(self
, account
, agent
):
1053 # remove transport's contacts from treeview
1054 jid_list
= gajim
.contacts
.get_jid_list(account
)
1055 for jid
in jid_list
:
1056 if jid
.endswith('@' + agent
):
1057 c
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
1059 'Removing contact %s due to unregistered transport %s'\
1061 gajim
.connections
[account
].unsubscribe(c
.jid
)
1062 # Transport contacts can't have 2 resources
1063 if c
.jid
in gajim
.to_be_removed
[account
]:
1064 # This way we'll really remove it
1065 gajim
.to_be_removed
[account
].remove(c
.jid
)
1066 self
.roster
.remove_contact(c
.jid
, account
, backend
=True)
1068 def handle_event_register_agent_info(self
, account
, array
):
1069 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
1070 # info in a dataform if is_form is True
1071 if array
[2] or 'instructions' in array
[1]:
1072 config
.ServiceRegistrationWindow(array
[0], array
[1], account
,
1075 dialogs
.ErrorDialog(_('Contact with "%s" cannot be established') \
1076 % array
[0], _('Check your connection or try again later.'))
1078 def handle_event_agent_info_items(self
, account
, array
):
1079 #('AGENT_INFO_ITEMS', account, (agent, node, items))
1080 our_jid
= gajim
.get_jid_from_account(account
)
1081 if 'pep_services' in gajim
.interface
.instances
[account
] and \
1082 array
[0] == our_jid
:
1083 gajim
.interface
.instances
[account
]['pep_services'].items_received(
1086 gajim
.connections
[account
].services_cache
.agent_items(array
[0],
1088 except AttributeError:
1091 def handle_event_agent_info_info(self
, account
, array
):
1092 #('AGENT_INFO_INFO', account, (agent, node, identities, features, data))
1094 gajim
.connections
[account
].services_cache
.agent_info(array
[0],
1095 array
[1], array
[2], array
[3], array
[4])
1096 except AttributeError:
1099 def handle_event_new_acc_connected(self
, account
, array
):
1100 #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err,
1101 # ssl_cert, ssl_fingerprint))
1102 if 'account_creation_wizard' in self
.instances
:
1103 self
.instances
['account_creation_wizard'].new_acc_connected(array
[0],
1104 array
[1], array
[2], array
[3], array
[4], array
[5])
1106 def handle_event_new_acc_not_connected(self
, account
, array
):
1107 #('NEW_ACC_NOT_CONNECTED', account, (reason))
1108 if 'account_creation_wizard' in self
.instances
:
1109 self
.instances
['account_creation_wizard'].new_acc_not_connected(array
)
1111 def handle_event_acc_ok(self
, account
, array
):
1112 #('ACC_OK', account, (config))
1113 if 'account_creation_wizard' in self
.instances
:
1114 self
.instances
['account_creation_wizard'].acc_is_ok(array
)
1116 if self
.remote_ctrl
:
1117 self
.remote_ctrl
.raise_signal('NewAccount', (account
, array
))
1119 def handle_event_acc_not_ok(self
, account
, array
):
1120 #('ACC_NOT_OK', account, (reason))
1121 if 'account_creation_wizard' in self
.instances
:
1122 self
.instances
['account_creation_wizard'].acc_is_not_ok(array
)
1124 def handle_event_quit(self
, p1
, p2
):
1125 self
.roster
.quit_gtkgui_interface()
1127 def handle_event_myvcard(self
, account
, array
):
1129 if 'NICKNAME' in array
and array
['NICKNAME']:
1130 gajim
.nicks
[account
] = array
['NICKNAME']
1131 elif 'FN' in array
and array
['FN']:
1132 gajim
.nicks
[account
] = array
['FN']
1133 if 'profile' in self
.instances
[account
]:
1134 win
= self
.instances
[account
]['profile']
1135 win
.set_values(array
)
1136 if account
in self
.show_vcard_when_connect
:
1137 self
.show_vcard_when_connect
.remove(account
)
1139 if jid
in self
.instances
[account
]['infos']:
1140 self
.instances
[account
]['infos'][jid
].set_values(array
)
1142 def handle_event_vcard(self
, account
, vcard
):
1143 # ('VCARD', account, data)
1144 '''vcard holds the vcard data'''
1146 resource
= vcard
.get('resource', '')
1147 fjid
= jid
+ '/' + str(resource
)
1151 if jid
in self
.instances
[account
]['infos']:
1152 win
= self
.instances
[account
]['infos'][jid
]
1153 elif resource
and fjid
in self
.instances
[account
]['infos']:
1154 win
= self
.instances
[account
]['infos'][fjid
]
1156 win
.set_values(vcard
)
1158 # show avatar in chat
1160 if resource
and self
.msg_win_mgr
.has_window(fjid
, account
):
1161 win
= self
.msg_win_mgr
.get_window(fjid
, account
)
1162 ctrl
= win
.get_control(fjid
, account
)
1163 elif self
.msg_win_mgr
.has_window(jid
, account
):
1164 win
= self
.msg_win_mgr
.get_window(jid
, account
)
1165 ctrl
= win
.get_control(jid
, account
)
1167 if ctrl
and ctrl
.type_id
!= message_control
.TYPE_GC
:
1170 # Show avatar in roster or gc_roster
1171 gc_ctrl
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
1172 if not gc_ctrl
and \
1173 jid
in self
.minimized_controls
[account
]:
1174 gc_ctrl
= self
.minimized_controls
[account
][jid
]
1175 if gc_ctrl
and gc_ctrl
.type_id
== message_control
.TYPE_GC
:
1176 gc_ctrl
.draw_avatar(resource
)
1178 self
.roster
.draw_avatar(jid
, account
)
1179 if self
.remote_ctrl
:
1180 self
.remote_ctrl
.raise_signal('VcardInfo', (account
, vcard
))
1182 def handle_event_last_status_time(self
, account
, array
):
1183 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
1189 if array
[0] in self
.instances
[account
]['infos']:
1190 win
= self
.instances
[account
]['infos'][array
[0]]
1191 elif array
[0] + '/' + array
[1] in self
.instances
[account
]['infos']:
1192 win
= self
.instances
[account
]['infos'][array
[0] + '/' + array
[1]]
1193 c
= gajim
.contacts
.get_contact(account
, array
[0], array
[1])
1194 if c
: # c can be none if it's a gc contact
1195 c
.last_status_time
= time
.localtime(time
.time() - tim
)
1198 self
.roster
.draw_contact(c
.jid
, account
) # draw offline status
1200 win
.set_last_status_time()
1201 if self
.remote_ctrl
:
1202 self
.remote_ctrl
.raise_signal('LastStatusTime', (account
, array
))
1204 def handle_event_os_info(self
, account
, array
):
1205 #'OS_INFO' (account, (jid, resource, client_info, os_info))
1207 if array
[0] in self
.instances
[account
]['infos']:
1208 win
= self
.instances
[account
]['infos'][array
[0]]
1209 elif array
[0] + '/' + array
[1] in self
.instances
[account
]['infos']:
1210 win
= self
.instances
[account
]['infos'][array
[0] + '/' + array
[1]]
1212 win
.set_os_info(array
[1], array
[2], array
[3])
1213 if self
.remote_ctrl
:
1214 self
.remote_ctrl
.raise_signal('OsInfo', (account
, array
))
1216 def handle_event_entity_time(self
, account
, array
):
1217 #'ENTITY_TIME' (account, (jid, resource, time_info))
1219 if array
[0] in self
.instances
[account
]['infos']:
1220 win
= self
.instances
[account
]['infos'][array
[0]]
1221 elif array
[0] + '/' + array
[1] in self
.instances
[account
]['infos']:
1222 win
= self
.instances
[account
]['infos'][array
[0] + '/' + array
[1]]
1224 win
.set_entity_time(array
[1], array
[2])
1225 if self
.remote_ctrl
:
1226 self
.remote_ctrl
.raise_signal('EntityTime', (account
, array
))
1228 def handle_event_gc_notify(self
, account
, array
):
1229 #'GC_NOTIFY' (account, (room_jid, show, status, nick,
1230 # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha))
1235 fjid
= room_jid
+ '/' + nick
1238 conn
= gajim
.connections
[account
]
1240 # Get the window and control for the updated status, this may be a
1241 # PrivateChatControl
1242 control
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
1244 if not control
and \
1245 room_jid
in self
.minimized_controls
[account
]:
1246 control
= self
.minimized_controls
[account
][room_jid
]
1248 if not control
or (control
and control
.type_id
!= message_control
.TYPE_GC
):
1251 control
.chg_contact_status(nick
, show
, status
, array
[4], array
[5],
1252 array
[6], array
[7], array
[8], array
[9], array
[10], array
[11])
1254 contact
= gajim
.contacts
.\
1255 get_contact_with_highest_priority(account
, room_jid
)
1257 self
.roster
.draw_contact(room_jid
, account
)
1259 # print status in chat window and update status/GPG image
1260 ctrl
= self
.msg_win_mgr
.get_control(fjid
, account
)
1262 statusCode
= array
[9]
1263 if '303' in statusCode
:
1264 new_nick
= array
[10]
1265 ctrl
.print_conversation(_('%(nick)s is now known as %(new_nick)s') \
1266 % {'nick': nick
, 'new_nick': new_nick
}, 'status')
1267 gc_c
= gajim
.contacts
.get_gc_contact(account
, room_jid
, new_nick
)
1268 c
= gajim
.contacts
.contact_from_gc_contact(gc_c
)
1269 ctrl
.gc_contact
= gc_c
1273 if ctrl
.session
.enable_encryption
:
1274 thread_id
= ctrl
.session
.thread_id
1275 ctrl
.session
.terminate_e2e()
1276 conn
.delete_session(fjid
, thread_id
)
1277 ctrl
.no_autonegotiation
= False
1279 old_jid
= room_jid
+ '/' + nick
1280 new_jid
= room_jid
+ '/' + new_nick
1281 self
.msg_win_mgr
.change_key(old_jid
, new_jid
, account
)
1283 contact
= ctrl
.contact
1285 contact
.status
= status
1286 gc_contact
= ctrl
.gc_contact
1287 gc_contact
.show
= show
1288 gc_contact
.status
= status
1289 uf_show
= helpers
.get_uf_show(show
)
1290 ctrl
.print_conversation(_('%(nick)s is now %(status)s') % {
1291 'nick': nick
, 'status': uf_show
}, 'status')
1293 ctrl
.print_conversation(' (', 'status', simple
=True)
1294 ctrl
.print_conversation('%s' % (status
), 'status', simple
=True)
1295 ctrl
.print_conversation(')', 'status', simple
=True)
1296 ctrl
.parent_win
.redraw_tab(ctrl
)
1298 if self
.remote_ctrl
:
1299 self
.remote_ctrl
.raise_signal('GCPresence', (account
, array
))
1301 def handle_event_gc_msg(self
, account
, array
):
1302 # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg,
1304 jids
= array
[0].split('/', 1)
1309 gc_control
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
1310 if not gc_control
and \
1311 room_jid
in self
.minimized_controls
[account
]:
1312 gc_control
= self
.minimized_controls
[account
][room_jid
]
1318 if gajim
.config
.get('ignore_incoming_xhtml'):
1321 # message from server
1324 # message from someone
1327 gc_control
.on_message(nick
, msg
, array
[2], array
[3], xhtml
, array
[5])
1329 if self
.remote_ctrl
:
1330 highlight
= gc_control
.needs_visual_notification(msg
)
1331 array
+= (highlight
,)
1332 self
.remote_ctrl
.raise_signal('GCMessage', (account
, array
))
1334 def handle_event_gc_subject(self
, account
, array
):
1335 #('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
1336 jids
= array
[0].split('/', 1)
1339 gc_control
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
1341 if not gc_control
and \
1342 jid
in self
.minimized_controls
[account
]:
1343 gc_control
= self
.minimized_controls
[account
][jid
]
1345 contact
= gajim
.contacts
.\
1346 get_contact_with_highest_priority(account
, jid
)
1348 contact
.status
= array
[1]
1349 self
.roster
.draw_contact(jid
, account
)
1353 gc_control
.set_subject(array
[1])
1354 # Standard way, the message comes from the occupant who set the subject
1357 text
= _('%(jid)s has set the subject to %(subject)s') % {
1358 'jid': jids
[1], 'subject': array
[1]}
1359 # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be
1360 # deleted one day. We can receive a subject with a body that contains
1361 # "X has set the subject to Y" ...
1364 if text
is not None:
1366 gc_control
.print_old_conversation(text
)
1368 gc_control
.print_conversation(text
)
1370 def handle_event_gc_config(self
, account
, array
):
1371 #('GC_CONFIG', account, (jid, form)) config is a dict
1372 room_jid
= array
[0].split('/')[0]
1373 if room_jid
in gajim
.automatic_rooms
[account
]:
1374 if 'continue_tag' in gajim
.automatic_rooms
[account
][room_jid
]:
1375 # We're converting chat to muc. allow participants to invite
1376 form
= dataforms
.ExtendForm(node
= array
[1])
1377 for f
in form
.iter_fields():
1378 if f
.var
== 'muc#roomconfig_allowinvites':
1380 elif f
.var
== 'muc#roomconfig_publicroom':
1382 elif f
.var
== 'muc#roomconfig_membersonly':
1384 elif f
.var
== 'public_list':
1386 gajim
.connections
[account
].send_gc_config(room_jid
, form
)
1388 # use default configuration
1389 gajim
.connections
[account
].send_gc_config(room_jid
, array
[1])
1391 # check if it is necessary to add <continue />
1392 continue_tag
= False
1393 if 'continue_tag' in gajim
.automatic_rooms
[account
][room_jid
]:
1395 if 'invities' in gajim
.automatic_rooms
[account
][room_jid
]:
1396 for jid
in gajim
.automatic_rooms
[account
][room_jid
]['invities']:
1397 gajim
.connections
[account
].send_invite(room_jid
, jid
,
1398 continue_tag
=continue_tag
)
1399 del gajim
.automatic_rooms
[account
][room_jid
]
1400 elif room_jid
not in self
.instances
[account
]['gc_config']:
1401 self
.instances
[account
]['gc_config'][room_jid
] = \
1402 config
.GroupchatConfigWindow(account
, room_jid
, array
[1])
1404 def handle_event_gc_config_change(self
, account
, array
):
1405 #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list
1406 # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
1407 # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init
1409 statusCode
= array
[1]
1411 gc_control
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
1412 if not gc_control
and \
1413 jid
in self
.minimized_controls
[account
]:
1414 gc_control
= self
.minimized_controls
[account
][jid
]
1419 if '100' in statusCode
:
1420 # Can be a presence (see chg_contact_status in groupchat_control.py)
1421 changes
.append(_('Any occupant is allowed to see your full JID'))
1422 gc_control
.is_anonymous
= False
1423 if '102' in statusCode
:
1424 changes
.append(_('Room now shows unavailable member'))
1425 if '103' in statusCode
:
1426 changes
.append(_('room now does not show unavailable members'))
1427 if '104' in statusCode
:
1429 _('A non-privacy-related room configuration change has occurred'))
1430 if '170' in statusCode
:
1431 # Can be a presence (see chg_contact_status in groupchat_control.py)
1432 changes
.append(_('Room logging is now enabled'))
1433 if '171' in statusCode
:
1434 changes
.append(_('Room logging is now disabled'))
1435 if '172' in statusCode
:
1436 changes
.append(_('Room is now non-anonymous'))
1437 gc_control
.is_anonymous
= False
1438 if '173' in statusCode
:
1439 changes
.append(_('Room is now semi-anonymous'))
1440 gc_control
.is_anonymous
= True
1441 if '174' in statusCode
:
1442 changes
.append(_('Room is now fully-anonymous'))
1443 gc_control
.is_anonymous
= True
1445 for change
in changes
:
1446 gc_control
.print_conversation(change
)
1448 def handle_event_gc_affiliation(self
, account
, array
):
1449 #('GC_AFFILIATION', account, (room_jid, users_dict))
1451 if room_jid
in self
.instances
[account
]['gc_config']:
1452 self
.instances
[account
]['gc_config'][room_jid
].\
1453 affiliation_list_received(array
[1])
1455 def handle_event_gc_password_required(self
, account
, array
):
1456 #('GC_PASSWORD_REQUIRED', account, (room_jid, nick))
1461 gajim
.connections
[account
].join_gc(nick
, room_jid
, text
)
1462 gajim
.gc_passwords
[room_jid
] = text
1465 # get and destroy window
1466 if room_jid
in gajim
.interface
.minimized_controls
[account
]:
1467 self
.roster
.on_disconnect(None, room_jid
, account
)
1469 win
= self
.msg_win_mgr
.get_window(room_jid
, account
)
1470 ctrl
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
1471 win
.remove_tab(ctrl
, 3)
1473 dlg
= dialogs
.InputDialog(_('Password Required'),
1474 _('A Password is required to join the room %s. Please type it.') % \
1475 room_jid
, is_modal
=False, ok_handler
=on_ok
, cancel_handler
=on_cancel
)
1476 dlg
.input_entry
.set_visibility(False)
1478 def handle_event_gc_invitation(self
, account
, array
):
1479 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
1480 jid
= gajim
.get_jid_without_resource(array
[1])
1482 if helpers
.allow_popup_window(account
) or not self
.systray_enabled
:
1483 dialogs
.InvitationReceivedDialog(account
, room_jid
, jid
, array
[3],
1484 array
[2], is_continued
=array
[4])
1487 self
.add_event(account
, jid
, 'gc-invitation', (room_jid
, array
[2],
1488 array
[3], array
[4]))
1490 if helpers
.allow_showing_notification(account
):
1491 path
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events',
1492 'gc_invitation.png')
1493 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(path
)
1494 event_type
= _('Groupchat Invitation')
1495 notify
.popup(event_type
, jid
, account
, 'gc-invitation', path
,
1496 event_type
, room_jid
)
1498 def forget_gpg_passphrase(self
, keyid
):
1499 if keyid
in self
.gpg_passphrase
:
1500 del self
.gpg_passphrase
[keyid
]
1503 def handle_event_bad_passphrase(self
, account
, array
):
1504 #('BAD_PASSPHRASE', account, ())
1505 use_gpg_agent
= gajim
.config
.get('use_gpg_agent')
1508 sectext
= _('You configured Gajim to use GPG agent, but there is no '
1509 'GPG agent running or it returned a wrong passphrase.\n')
1510 sectext
+= _('You are currently connected without your OpenPGP key.')
1511 keyID
= gajim
.config
.get_per('accounts', account
, 'keyid')
1512 self
.forget_gpg_passphrase(keyID
)
1513 dialogs
.WarningDialog(_('Your passphrase is incorrect'), sectext
)
1515 def handle_event_gpg_password_required(self
, account
, array
):
1516 #('GPG_PASSWORD_REQUIRED', account, (callback,))
1518 keyid
= gajim
.config
.get_per('accounts', account
, 'keyid')
1519 if keyid
in self
.gpg_passphrase
:
1520 request
= self
.gpg_passphrase
[keyid
]
1522 request
= PassphraseRequest(keyid
)
1523 self
.gpg_passphrase
[keyid
] = request
1524 request
.add_callback(account
, callback
)
1526 def handle_event_gpg_always_trust(self
, account
, callback
):
1527 #('GPG_ALWAYS_TRUST', account, callback)
1528 def on_yes(checked
):
1530 gajim
.connections
[account
].gpg
.always_trust
= True
1536 dialogs
.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
1537 'encrypt this chat is not trusted. Do you really want to encrypt this '
1538 'message?'), checktext
=_('Do _not ask me again'),
1539 on_response_yes
=on_yes
, on_response_no
=on_no
)
1541 def handle_event_password_required(self
, account
, array
):
1542 #('PASSWORD_REQUIRED', account, None)
1543 if account
in self
.pass_dialog
:
1545 text
= _('Enter your password for account %s') % account
1546 if passwords
.USER_HAS_GNOMEKEYRING
and \
1547 not passwords
.USER_USES_GNOMEKEYRING
:
1548 text
+= '\n' + _('Gnome Keyring is installed but not \
1549 correctly started (environment variable probably not \
1552 def on_ok(passphrase
, save
):
1554 gajim
.config
.set_per('accounts', account
, 'savepass', True)
1555 passwords
.save_password(account
, passphrase
)
1556 gajim
.connections
[account
].set_password(passphrase
)
1557 del self
.pass_dialog
[account
]
1560 self
.roster
.set_state(account
, 'offline')
1561 self
.roster
.update_status_combobox()
1562 del self
.pass_dialog
[account
]
1564 self
.pass_dialog
[account
] = dialogs
.PassphraseDialog(
1565 _('Password Required'), text
, _('Save password'), ok_handler
=on_ok
,
1566 cancel_handler
=on_cancel
)
1568 def handle_event_roster_info(self
, account
, array
):
1569 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
1575 contacts
= gajim
.contacts
.get_contacts(account
, jid
)
1576 if (not sub
or sub
== 'none') and (not ask
or ask
== 'none') and \
1577 not name
and not groups
:
1578 # contact removed us.
1580 self
.roster
.remove_contact(jid
, account
, backend
=True)
1585 # Add new contact to roster
1586 contact
= gajim
.contacts
.create_contact(jid
=jid
, name
=name
,
1587 groups
=groups
, show
='offline', sub
=sub
, ask
=ask
)
1588 gajim
.contacts
.add_contact(account
, contact
)
1589 self
.roster
.add_contact(jid
, account
)
1591 # it is an existing contact that might has changed
1593 # If contact has changed (sub, ask or group) update roster
1594 # Mind about observer status changes:
1595 # According to xep 0162, a contact is not an observer anymore when
1596 # we asked for auth, so also remove him if ask changed
1597 old_groups
= contacts
[0].groups
1598 if contacts
[0].sub
!= sub
or contacts
[0].ask
!= ask\
1599 or old_groups
!= groups
:
1601 # c.get_shown_groups() has changed. Reflect that in roster_winodow
1602 self
.roster
.remove_contact(jid
, account
, force
=True)
1603 for contact
in contacts
:
1604 contact
.name
= name
or ''
1607 contact
.groups
= groups
or []
1609 self
.roster
.add_contact(jid
, account
)
1610 # Refilter and update old groups
1611 for group
in old_groups
:
1612 self
.roster
.draw_group(group
, account
)
1614 self
.roster
.draw_contact(jid
, account
)
1616 if self
.remote_ctrl
:
1617 self
.remote_ctrl
.raise_signal('RosterInfo', (account
, array
))
1619 def handle_event_bookmarks(self
, account
, bms
):
1620 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
1621 # We received a bookmark item from the server (JEP48)
1622 # Auto join GC windows if neccessary
1624 self
.roster
.set_actions_menu_needs_rebuild()
1625 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
1626 # do not autojoin if we are invisible
1627 if gajim
.connections
[account
].connected
== invisible_show
:
1630 self
.auto_join_bookmarks(account
)
1632 def handle_event_file_send_error(self
, account
, array
):
1634 file_props
= array
[1]
1635 ft
= self
.instances
['file_transfers']
1636 ft
.set_status(file_props
['type'], file_props
['sid'], 'stop')
1638 if helpers
.allow_popup_window(account
):
1639 ft
.show_send_error(file_props
)
1642 self
.add_event(account
, jid
, 'file-send-error', file_props
)
1644 if helpers
.allow_showing_notification(account
):
1645 img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events', 'ft_error.png')
1646 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(img
)
1647 event_type
= _('File Transfer Error')
1648 notify
.popup(event_type
, jid
, account
, 'file-send-error', path
,
1649 event_type
, file_props
['name'])
1651 def handle_event_gmail_notify(self
, account
, array
):
1653 gmail_new_messages
= int(array
[1])
1654 gmail_messages_list
= array
[2]
1655 if gajim
.config
.get('notify_on_new_gmail_email'):
1656 img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events',
1657 'new_email_recv.png')
1658 title
= _('New mail on %(gmail_mail_address)s') % \
1659 {'gmail_mail_address': jid
}
1660 text
= i18n
.ngettext('You have %d new mail conversation',
1661 'You have %d new mail conversations', gmail_new_messages
,
1662 gmail_new_messages
, gmail_new_messages
)
1664 if gajim
.config
.get('notify_on_new_gmail_email_extra'):
1666 for gmessage
in gmail_messages_list
:
1667 #FIXME: emulate Gtalk client popups. find out what they parse and
1668 # how they decide what to show each message has a 'From',
1669 # 'Subject' and 'Snippet' field
1672 senders
= ',\n '.join(reversed(gmessage
['From']))
1673 text
+= _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \
1674 {'from_address': senders
, 'subject': gmessage
['Subject'],
1675 'snippet': gmessage
['Snippet']}
1678 if gajim
.config
.get_per('soundevents', 'gmail_received', 'enabled'):
1679 helpers
.play_sound('gmail_received')
1680 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(img
)
1681 notify
.popup(_('New E-mail'), jid
, account
, 'gmail',
1682 path_to_image
=path
, title
=title
,
1685 if self
.remote_ctrl
:
1686 self
.remote_ctrl
.raise_signal('NewGmail', (account
, array
))
1688 def handle_event_file_request_error(self
, account
, array
):
1689 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
1690 jid
, file_props
, errmsg
= array
1691 ft
= self
.instances
['file_transfers']
1692 ft
.set_status(file_props
['type'], file_props
['sid'], 'stop')
1693 errno
= file_props
['error']
1695 if helpers
.allow_popup_window(account
):
1696 if errno
in (-4, -5):
1697 ft
.show_stopped(jid
, file_props
, errmsg
)
1699 ft
.show_request_error(file_props
)
1702 if errno
in (-4, -5):
1703 msg_type
= 'file-error'
1705 msg_type
= 'file-request-error'
1707 self
.add_event(account
, jid
, msg_type
, file_props
)
1709 if helpers
.allow_showing_notification(account
):
1710 # check if we should be notified
1711 img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events', 'ft_error.png')
1713 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(img
)
1714 event_type
= _('File Transfer Error')
1715 notify
.popup(event_type
, jid
, account
, msg_type
, path
,
1716 title
= event_type
, text
= file_props
['name'])
1718 def handle_event_file_request(self
, account
, array
):
1720 if jid
not in gajim
.contacts
.get_jid_list(account
):
1722 attached_keys
= gajim
.config
.get_per('accounts', account
,
1723 'attached_gpg_keys').split()
1724 if jid
in attached_keys
:
1725 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
1726 contact
= gajim
.contacts
.create_contact(jid
=jid
, name
='',
1727 groups
=[_('Not in Roster')], show
='not in roster', status
='',
1728 sub
='none', keyID
=keyID
)
1729 gajim
.contacts
.add_contact(account
, contact
)
1730 self
.roster
.add_contact(contact
.jid
, account
)
1731 file_props
= array
[1]
1732 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
1734 if helpers
.allow_popup_window(account
):
1735 self
.instances
['file_transfers'].show_file_request(account
, contact
,
1739 self
.add_event(account
, jid
, 'file-request', file_props
)
1741 if helpers
.allow_showing_notification(account
):
1742 img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events',
1744 txt
= _('%s wants to send you a file.') % gajim
.get_name_from_jid(
1746 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(img
)
1747 event_type
= _('File Transfer Request')
1748 notify
.popup(event_type
, jid
, account
, 'file-request',
1749 path_to_image
= path
, title
= event_type
, text
= txt
)
1751 def handle_event_file_error(self
, title
, message
):
1752 dialogs
.ErrorDialog(title
, message
)
1754 def handle_event_file_progress(self
, account
, file_props
):
1755 if time
.time() - self
.last_ftwindow_update
> 0.5:
1756 # update ft window every 500ms
1757 self
.last_ftwindow_update
= time
.time()
1758 self
.instances
['file_transfers'].set_progress(file_props
['type'],
1759 file_props
['sid'], file_props
['received-len'])
1761 def handle_event_file_rcv_completed(self
, account
, file_props
):
1762 ft
= self
.instances
['file_transfers']
1763 if file_props
['error'] == 0:
1764 ft
.set_progress(file_props
['type'], file_props
['sid'],
1765 file_props
['received-len'])
1767 ft
.set_status(file_props
['type'], file_props
['sid'], 'stop')
1768 if 'stalled' in file_props
and file_props
['stalled'] or \
1769 'paused' in file_props
and file_props
['paused']:
1771 if file_props
['type'] == 'r': # we receive a file
1772 jid
= unicode(file_props
['sender'])
1773 else: # we send a file
1774 jid
= unicode(file_props
['receiver'])
1776 if helpers
.allow_popup_window(account
):
1777 if file_props
['error'] == 0:
1778 if gajim
.config
.get('notify_on_file_complete'):
1779 ft
.show_completed(jid
, file_props
)
1780 elif file_props
['error'] == -1:
1781 ft
.show_stopped(jid
, file_props
,
1782 error_msg
=_('Remote contact stopped transfer'))
1783 elif file_props
['error'] == -6:
1784 ft
.show_stopped(jid
, file_props
, error_msg
=_('Error opening file'))
1789 if file_props
['error'] == 0 and gajim
.config
.get(
1790 'notify_on_file_complete'):
1791 msg_type
= 'file-completed'
1792 event_type
= _('File Transfer Completed')
1793 elif file_props
['error'] in (-1, -6):
1794 msg_type
= 'file-stopped'
1795 event_type
= _('File Transfer Stopped')
1797 if event_type
== '':
1798 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
1799 # this should never happen but it does. see process_result() in socks5.py
1800 # who calls this func (sth is really wrong unless this func is also registered
1805 self
.add_event(account
, jid
, msg_type
, file_props
)
1807 if file_props
is not None:
1808 if file_props
['type'] == 'r':
1809 # get the name of the sender, as it is in the roster
1810 sender
= unicode(file_props
['sender']).split('/')[0]
1811 name
= gajim
.contacts
.get_first_contact_from_jid(account
,
1812 sender
).get_shown_name()
1813 filename
= os
.path
.basename(file_props
['file-name'])
1814 if event_type
== _('File Transfer Completed'):
1815 txt
= _('You successfully received %(filename)s from %(name)s.')\
1816 % {'filename': filename
, 'name': name
}
1819 txt
= _('File transfer of %(filename)s from %(name)s stopped.')\
1820 % {'filename': filename
, 'name': name
}
1821 img
= 'ft_stopped.png'
1823 receiver
= file_props
['receiver']
1824 if hasattr(receiver
, 'jid'):
1825 receiver
= receiver
.jid
1826 receiver
= receiver
.split('/')[0]
1827 # get the name of the contact, as it is in the roster
1828 name
= gajim
.contacts
.get_first_contact_from_jid(account
,
1829 receiver
).get_shown_name()
1830 filename
= os
.path
.basename(file_props
['file-name'])
1831 if event_type
== _('File Transfer Completed'):
1832 txt
= _('You successfully sent %(filename)s to %(name)s.')\
1833 % {'filename': filename
, 'name': name
}
1836 txt
= _('File transfer of %(filename)s to %(name)s stopped.')\
1837 % {'filename': filename
, 'name': name
}
1838 img
= 'ft_stopped.png'
1839 img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'events', img
)
1840 path
= gtkgui_helpers
.get_path_to_generic_or_avatar(img
)
1844 if gajim
.config
.get('notify_on_file_complete') and \
1845 (gajim
.config
.get('autopopupaway') or \
1846 gajim
.connections
[account
].connected
in (2, 3)):
1847 # we want to be notified and we are online/chat or we don't mind
1848 # bugged when away/na/busy
1849 notify
.popup(event_type
, jid
, account
, msg_type
, path_to_image
= path
,
1850 title
= event_type
, text
= txt
)
1852 def handle_event_stanza_arrived(self
, account
, stanza
):
1853 if account
not in self
.instances
:
1855 if 'xml_console' in self
.instances
[account
]:
1856 self
.instances
[account
]['xml_console'].print_stanza(stanza
, 'incoming')
1858 def handle_event_stanza_sent(self
, account
, stanza
):
1859 if account
not in self
.instances
:
1861 if 'xml_console' in self
.instances
[account
]:
1862 self
.instances
[account
]['xml_console'].print_stanza(stanza
, 'outgoing')
1864 def handle_event_vcard_published(self
, account
, array
):
1865 if 'profile' in self
.instances
[account
]:
1866 win
= self
.instances
[account
]['profile']
1867 win
.vcard_published()
1868 for gc_control
in self
.msg_win_mgr
.get_controls(message_control
.TYPE_GC
) + \
1869 self
.minimized_controls
[account
].values():
1870 if gc_control
.account
== account
:
1871 show
= gajim
.SHOW_LIST
[gajim
.connections
[account
].connected
]
1872 status
= gajim
.connections
[account
].status
1873 gajim
.connections
[account
].send_gc_status(gc_control
.nick
,
1874 gc_control
.room_jid
, show
, status
)
1876 def handle_event_vcard_not_published(self
, account
, array
):
1877 if 'profile' in self
.instances
[account
]:
1878 win
= self
.instances
[account
]['profile']
1879 win
.vcard_not_published()
1881 def ask_offline_status(self
, account
):
1882 for contact
in gajim
.contacts
.iter_contacts(account
):
1883 gajim
.connections
[account
].request_last_status_time(contact
.jid
,
1886 def handle_event_signed_in(self
, account
, empty
):
1887 '''SIGNED_IN event is emitted when we sign in, so handle it'''
1888 # block signed in notifications for 30 seconds
1889 gajim
.block_signed_in_notifications
[account
] = True
1890 self
.roster
.set_actions_menu_needs_rebuild()
1891 self
.roster
.draw_account(account
)
1892 state
= self
.sleeper
.getState()
1893 connected
= gajim
.connections
[account
].connected
1894 if gajim
.config
.get('ask_offline_status_on_connection'):
1895 # Ask offline status in 1 minute so w'are sure we got all online
1897 gobject
.timeout_add_seconds(60, self
.ask_offline_status
, account
)
1898 if state
!= common
.sleepy
.STATE_UNKNOWN
and connected
in (2, 3):
1899 # we go online or free for chat, so we activate auto status
1900 gajim
.sleeper_state
[account
] = 'online'
1901 elif not ((state
== common
.sleepy
.STATE_AWAY
and connected
== 4) or \
1902 (state
== common
.sleepy
.STATE_XA
and connected
== 5)):
1903 # If we are autoaway/xa and come back after a disconnection, do nothing
1904 # Else disable autoaway
1905 gajim
.sleeper_state
[account
] = 'off'
1906 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
1907 # We cannot join rooms if we are invisible
1908 if gajim
.connections
[account
].connected
== invisible_show
:
1910 # join already open groupchats
1911 for gc_control
in self
.msg_win_mgr
.get_controls(message_control
.TYPE_GC
) \
1912 + self
.minimized_controls
[account
].values():
1913 if account
!= gc_control
.account
:
1915 room_jid
= gc_control
.room_jid
1916 if room_jid
in gajim
.gc_connected
[account
] and \
1917 gajim
.gc_connected
[account
][room_jid
]:
1919 nick
= gc_control
.nick
1920 password
= gajim
.gc_passwords
.get(room_jid
, '')
1921 gajim
.connections
[account
].join_gc(nick
, room_jid
, password
)
1923 def handle_event_metacontacts(self
, account
, tags_list
):
1924 gajim
.contacts
.define_metacontacts(account
, tags_list
)
1925 self
.roster
.redraw_metacontacts(account
)
1927 def handle_atom_entry(self
, account
, data
):
1929 AtomWindow
.newAtomEntry(atom_entry
)
1931 def handle_event_failed_decrypt(self
, account
, data
):
1932 jid
, tim
, session
= data
1934 details
= _('Unable to decrypt message from '
1935 '%s\nIt may have been tampered with.') % jid
1937 ctrl
= session
.control
1939 ctrl
.print_conversation_line(details
, 'status', '', tim
)
1941 dialogs
.WarningDialog(_('Unable to decrypt message'),
1944 # terminate the session
1945 session
.terminate_e2e()
1946 session
.conn
.delete_session(jid
, session
.thread_id
)
1948 # restart the session
1950 ctrl
.begin_e2e_negotiation()
1952 def handle_event_privacy_lists_received(self
, account
, data
):
1953 # ('PRIVACY_LISTS_RECEIVED', account, list)
1954 if account
not in self
.instances
:
1956 if 'privacy_lists' in self
.instances
[account
]:
1957 self
.instances
[account
]['privacy_lists'].privacy_lists_received(data
)
1959 def handle_event_privacy_list_received(self
, account
, data
):
1960 # ('PRIVACY_LIST_RECEIVED', account, (name, rules))
1961 if account
not in self
.instances
:
1965 if 'privacy_list_%s' % name
in self
.instances
[account
]:
1966 self
.instances
[account
]['privacy_list_%s' % name
].\
1967 privacy_list_received(rules
)
1969 gajim
.connections
[account
].blocked_contacts
= []
1970 gajim
.connections
[account
].blocked_groups
= []
1971 gajim
.connections
[account
].blocked_list
= []
1972 gajim
.connections
[account
].blocked_all
= False
1974 if not 'type' in rule
:
1975 gajim
.connections
[account
].blocked_all
= True
1976 elif rule
['type'] == 'jid' and rule
['action'] == 'deny':
1977 gajim
.connections
[account
].blocked_contacts
.append(rule
['value'])
1978 elif rule
['type'] == 'group' and rule
['action'] == 'deny':
1979 gajim
.connections
[account
].blocked_groups
.append(rule
['value'])
1980 gajim
.connections
[account
].blocked_list
.append(rule
)
1981 #elif rule['type'] == "group" and action == "deny":
1982 # text_item = _('%s group "%s"') % _(rule['action']), rule['value']
1983 # self.store.append([text_item])
1984 # self.global_rules.append(rule)
1986 # self.global_rules_to_append.append(rule)
1987 if 'blocked_contacts' in self
.instances
[account
]:
1988 self
.instances
[account
]['blocked_contacts'].\
1989 privacy_list_received(rules
)
1991 def handle_event_privacy_lists_active_default(self
, account
, data
):
1994 # Send to all privacy_list_* windows as we can't know which one asked
1995 for win
in self
.instances
[account
]:
1996 if win
.startswith('privacy_list_'):
1997 self
.instances
[account
][win
].check_active_default(data
)
1999 def handle_event_privacy_list_removed(self
, account
, name
):
2000 # ('PRIVACY_LISTS_REMOVED', account, name)
2001 if account
not in self
.instances
:
2003 if 'privacy_lists' in self
.instances
[account
]:
2004 self
.instances
[account
]['privacy_lists'].privacy_list_removed(name
)
2006 def handle_event_zc_name_conflict(self
, account
, data
):
2007 def on_ok(new_name
):
2008 gajim
.config
.set_per('accounts', account
, 'name', new_name
)
2009 status
= gajim
.connections
[account
].status
2010 gajim
.connections
[account
].username
= new_name
2011 gajim
.connections
[account
].change_status(status
, '')
2013 gajim
.connections
[account
].change_status('offline','')
2015 dlg
= dialogs
.InputDialog(_('Username Conflict'),
2016 _('Please type a new username for your local account'), input_str
=data
,
2017 is_modal
=True, ok_handler
=on_ok
, cancel_handler
=on_cancel
)
2019 def handle_event_ping_sent(self
, account
, contact
):
2020 if contact
.jid
== contact
.get_full_jid():
2021 # If contact is a groupchat user
2022 jids
= [contact
.jid
]
2024 jids
= [contact
.jid
, contact
.get_full_jid()]
2026 ctrl
= self
.msg_win_mgr
.get_control(jid
, account
)
2028 ctrl
.print_conversation(_('Ping?'), 'status')
2030 def handle_event_ping_reply(self
, account
, data
):
2033 if contact
.jid
== contact
.get_full_jid():
2034 # If contact is a groupchat user
2035 jids
= [contact
.jid
]
2037 jids
= [contact
.jid
, contact
.get_full_jid()]
2039 ctrl
= self
.msg_win_mgr
.get_control(jid
, account
)
2041 ctrl
.print_conversation(_('Pong! (%s s.)') % seconds
, 'status')
2043 def handle_event_ping_error(self
, account
, contact
):
2044 if contact
.jid
== contact
.get_full_jid():
2045 # If contact is a groupchat user
2046 jids
= [contact
.jid
]
2048 jids
= [contact
.jid
, contact
.get_full_jid()]
2050 ctrl
= self
.msg_win_mgr
.get_control(jid
, account
)
2052 ctrl
.print_conversation(_('Error.'), 'status')
2054 def handle_event_search_form(self
, account
, data
):
2055 # ('SEARCH_FORM', account, (jid, dataform, is_dataform))
2056 if data
[0] not in self
.instances
[account
]['search']:
2058 self
.instances
[account
]['search'][data
[0]].on_form_arrived(data
[1],
2061 def handle_event_search_result(self
, account
, data
):
2062 # ('SEARCH_RESULT', account, (jid, dataform, is_dataform))
2063 if data
[0] not in self
.instances
[account
]['search']:
2065 self
.instances
[account
]['search'][data
[0]].on_result_arrived(data
[1],
2068 def handle_event_resource_conflict(self
, account
, data
):
2069 # ('RESOURCE_CONFLICT', account, ())
2070 # First we go offline, but we don't overwrite status message
2071 self
.roster
.send_status(account
, 'offline',
2072 gajim
.connections
[account
].status
)
2073 def on_ok(new_resource
):
2074 gajim
.config
.set_per('accounts', account
, 'resource', new_resource
)
2075 self
.roster
.send_status(account
, gajim
.connections
[account
].old_show
,
2076 gajim
.connections
[account
].status
)
2077 dlg
= dialogs
.InputDialog(_('Resource Conflict'),
2078 _('You are already connected to this account with the same resource. Please type a new one'), input_str
= gajim
.connections
[account
].server_resource
,
2079 is_modal
= False, ok_handler
= on_ok
)
2081 def handle_event_pep_config(self
, account
, data
):
2082 # ('PEP_CONFIG', account, (node, form))
2083 if 'pep_services' in self
.instances
[account
]:
2084 self
.instances
[account
]['pep_services'].config(data
[0], data
[1])
2086 def handle_event_roster_item_exchange(self
, account
, data
):
2087 # data = (action in [add, delete, modify], exchange_list, jid_from)
2088 dialogs
.RosterItemExchangeWindow(account
, data
[0], data
[1], data
[2])
2090 def handle_event_unique_room_id_supported(self
, account
, data
):
2091 '''Receive confirmation that unique_room_id are supported'''
2092 # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
2094 instance
.unique_room_id_supported(data
[0], data
[2])
2096 def handle_event_unique_room_id_unsupported(self
, account
, data
):
2097 # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance)
2099 instance
.unique_room_id_error(data
[0])
2101 def handle_event_ssl_error(self
, account
, data
):
2102 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
2103 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2105 def on_ok(is_checked
):
2106 del self
.instances
[account
]['online_dialog']['ssl_error']
2108 # Check if cert is already in file
2110 if os
.path
.isfile(gajim
.MY_CACERTS
):
2111 f
= open(gajim
.MY_CACERTS
)
2114 if data
[2] in certs
:
2115 dialogs
.ErrorDialog(_('Certificate Already in File'),
2116 _('This certificate is already in file %s, so it\'s not added again.') % gajim
.MY_CACERTS
)
2118 f
= open(gajim
.MY_CACERTS
, 'a')
2119 f
.write(server
+ '\n')
2120 f
.write(data
[2] + '\n\n')
2122 gajim
.config
.set_per('accounts', account
, 'ssl_fingerprint_sha1',
2125 ignore_ssl_errors
= gajim
.config
.get_per('accounts', account
,
2126 'ignore_ssl_errors').split()
2127 ignore_ssl_errors
.append(str(data
[1]))
2128 gajim
.config
.set_per('accounts', account
, 'ignore_ssl_errors',
2129 ' '.join(ignore_ssl_errors
))
2130 gajim
.connections
[account
].ssl_certificate_accepted()
2133 del self
.instances
[account
]['online_dialog']['ssl_error']
2134 gajim
.connections
[account
].disconnect(on_purpose
=True)
2135 self
.handle_event_status(account
, 'offline')
2137 pritext
= _('Error verifying SSL certificate')
2138 sectext
= _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data
[0]}
2139 if data
[1] in (18, 27):
2140 checktext1
= _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data
[3]
2143 checktext2
= _('Ignore this error for this certificate.')
2144 if 'ssl_error' in self
.instances
[account
]['online_dialog']:
2145 self
.instances
[account
]['online_dialog']['ssl_error'].destroy()
2146 self
.instances
[account
]['online_dialog']['ssl_error'] = \
2147 dialogs
.ConfirmationDialogDubbleCheck(pritext
, sectext
, checktext1
,
2148 checktext2
, on_response_ok
=on_ok
, on_response_cancel
=on_cancel
)
2150 def handle_event_fingerprint_error(self
, account
, data
):
2151 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
2152 def on_yes(is_checked
):
2153 del self
.instances
[account
]['online_dialog']['fingerprint_error']
2154 gajim
.config
.set_per('accounts', account
, 'ssl_fingerprint_sha1',
2156 # Reset the ignored ssl errors
2157 gajim
.config
.set_per('accounts', account
, 'ignore_ssl_errors', '')
2158 gajim
.connections
[account
].ssl_certificate_accepted()
2160 del self
.instances
[account
]['online_dialog']['fingerprint_error']
2161 gajim
.connections
[account
].disconnect(on_purpose
=True)
2162 self
.handle_event_status(account
, 'offline')
2163 pritext
= _('SSL certificate error')
2164 sectext
= _('It seems the SSL certificate of account %(account)s has '
2165 'changed or your connection is being hacked.\nOld fingerprint: %(old)s'
2166 '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update'
2167 ' the fingerprint of the certificate?') % {'account': account
,
2168 'old': gajim
.config
.get_per('accounts', account
,
2169 'ssl_fingerprint_sha1'), 'new': data
[0]}
2170 if 'fingerprint_error' in self
.instances
[account
]['online_dialog']:
2171 self
.instances
[account
]['online_dialog']['fingerprint_error'].destroy()
2172 self
.instances
[account
]['online_dialog']['fingerprint_error'] = \
2173 dialogs
.YesNoDialog(pritext
, sectext
, on_response_yes
=on_yes
,
2174 on_response_no
=on_no
)
2176 def handle_event_plain_connection(self
, account
, data
):
2177 # ('PLAIN_CONNECTION', account, (connection))
2178 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2179 def on_ok(is_checked
):
2180 if not is_checked
[0]:
2183 # On cancel call del self.instances, so don't call it another time
2185 del self
.instances
[account
]['online_dialog']['plain_connection']
2187 gajim
.config
.set_per('accounts', account
,
2188 'warn_when_plaintext_connection', False)
2189 gajim
.connections
[account
].connection_accepted(data
[0], 'plain')
2191 del self
.instances
[account
]['online_dialog']['plain_connection']
2192 gajim
.connections
[account
].disconnect(on_purpose
=True)
2193 self
.handle_event_status(account
, 'offline')
2194 pritext
= _('Insecure connection')
2195 sectext
= _('You are about to send your password on an unencrypted '
2196 'connection. Are you sure you want to do that?')
2197 checktext1
= _('Yes, I really want to connect insecurely')
2198 checktext2
= _('Do _not ask me again')
2199 if 'plain_connection' in self
.instances
[account
]['online_dialog']:
2200 self
.instances
[account
]['online_dialog']['plain_connection'].destroy()
2201 self
.instances
[account
]['online_dialog']['plain_connection'] = \
2202 dialogs
.ConfirmationDialogDubbleCheck(pritext
, sectext
,
2203 checktext1
, checktext2
, on_response_ok
=on_ok
,
2204 on_response_cancel
=on_cancel
, is_modal
=False)
2206 def handle_event_insecure_ssl_connection(self
, account
, data
):
2207 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
2208 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
2209 def on_ok(is_checked
):
2210 del self
.instances
[account
]['online_dialog']['insecure_ssl']
2211 if not is_checked
[0]:
2215 gajim
.config
.set_per('accounts', account
,
2216 'warn_when_insecure_ssl_connection', False)
2217 if gajim
.connections
[account
].connected
== 0:
2218 # We have been disconnecting (too long time since window is opened)
2219 # re-connect with auto-accept
2220 gajim
.connections
[account
].connection_auto_accepted
= True
2221 show
, msg
= gajim
.connections
[account
].continue_connect_info
[:2]
2222 self
.roster
.send_status(account
, show
, msg
)
2224 gajim
.connections
[account
].connection_accepted(data
[0], data
[1])
2226 del self
.instances
[account
]['online_dialog']['insecure_ssl']
2227 gajim
.connections
[account
].disconnect(on_purpose
=True)
2228 self
.handle_event_status(account
, 'offline')
2229 pritext
= _('Insecure connection')
2230 sectext
= _('You are about to send your password on an insecure '
2231 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?')
2232 checktext1
= _('Yes, I really want to connect insecurely')
2233 checktext2
= _('Do _not ask me again')
2234 if 'insecure_ssl' in self
.instances
[account
]['online_dialog']:
2235 self
.instances
[account
]['online_dialog']['insecure_ssl'].destroy()
2236 self
.instances
[account
]['online_dialog']['insecure_ssl'] = \
2237 dialogs
.ConfirmationDialogDubbleCheck(pritext
, sectext
,
2238 checktext1
, checktext2
, on_response_ok
=on_ok
,
2239 on_response_cancel
=on_cancel
, is_modal
=False)
2241 def handle_event_pubsub_node_removed(self
, account
, data
):
2242 # ('PUBSUB_NODE_REMOVED', account, (jid, node))
2243 if 'pep_services' in self
.instances
[account
]:
2244 if data
[0] == gajim
.get_jid_from_account(account
):
2245 self
.instances
[account
]['pep_services'].node_removed(data
[1])
2247 def handle_event_pubsub_node_not_removed(self
, account
, data
):
2248 # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg))
2249 if data
[0] == gajim
.get_jid_from_account(account
):
2250 dialogs
.WarningDialog(_('PEP node was not removed'),
2251 _('PEP node %(node)s was not removed: %(message)s') % {
2252 'node': data
[1], 'message': data
[2]})
2254 def register_handlers(self
):
2256 'ROSTER': self
.handle_event_roster
,
2257 'WARNING': self
.handle_event_warning
,
2258 'ERROR': self
.handle_event_error
,
2259 'INFORMATION': self
.handle_event_information
,
2260 'ERROR_ANSWER': self
.handle_event_error_answer
,
2261 'STATUS': self
.handle_event_status
,
2262 'NOTIFY': self
.handle_event_notify
,
2263 'MSGERROR': self
.handle_event_msgerror
,
2264 'MSGSENT': self
.handle_event_msgsent
,
2265 'MSGNOTSENT': self
.handle_event_msgnotsent
,
2266 'SUBSCRIBED': self
.handle_event_subscribed
,
2267 'UNSUBSCRIBED': self
.handle_event_unsubscribed
,
2268 'SUBSCRIBE': self
.handle_event_subscribe
,
2269 'AGENT_ERROR_INFO': self
.handle_event_agent_info_error
,
2270 'AGENT_ERROR_ITEMS': self
.handle_event_agent_items_error
,
2271 'AGENT_REMOVED': self
.handle_event_agent_removed
,
2272 'REGISTER_AGENT_INFO': self
.handle_event_register_agent_info
,
2273 'AGENT_INFO_ITEMS': self
.handle_event_agent_info_items
,
2274 'AGENT_INFO_INFO': self
.handle_event_agent_info_info
,
2275 'QUIT': self
.handle_event_quit
,
2276 'NEW_ACC_CONNECTED': self
.handle_event_new_acc_connected
,
2277 'NEW_ACC_NOT_CONNECTED': self
.handle_event_new_acc_not_connected
,
2278 'ACC_OK': self
.handle_event_acc_ok
,
2279 'ACC_NOT_OK': self
.handle_event_acc_not_ok
,
2280 'MYVCARD': self
.handle_event_myvcard
,
2281 'VCARD': self
.handle_event_vcard
,
2282 'LAST_STATUS_TIME': self
.handle_event_last_status_time
,
2283 'OS_INFO': self
.handle_event_os_info
,
2284 'ENTITY_TIME': self
.handle_event_entity_time
,
2285 'GC_NOTIFY': self
.handle_event_gc_notify
,
2286 'GC_MSG': self
.handle_event_gc_msg
,
2287 'GC_SUBJECT': self
.handle_event_gc_subject
,
2288 'GC_CONFIG': self
.handle_event_gc_config
,
2289 'GC_CONFIG_CHANGE': self
.handle_event_gc_config_change
,
2290 'GC_INVITATION': self
.handle_event_gc_invitation
,
2291 'GC_AFFILIATION': self
.handle_event_gc_affiliation
,
2292 'GC_PASSWORD_REQUIRED': self
.handle_event_gc_password_required
,
2293 'BAD_PASSPHRASE': self
.handle_event_bad_passphrase
,
2294 'ROSTER_INFO': self
.handle_event_roster_info
,
2295 'BOOKMARKS': self
.handle_event_bookmarks
,
2296 'CON_TYPE': self
.handle_event_con_type
,
2297 'CONNECTION_LOST': self
.handle_event_connection_lost
,
2298 'FILE_REQUEST': self
.handle_event_file_request
,
2299 'GMAIL_NOTIFY': self
.handle_event_gmail_notify
,
2300 'FILE_REQUEST_ERROR': self
.handle_event_file_request_error
,
2301 'FILE_SEND_ERROR': self
.handle_event_file_send_error
,
2302 'STANZA_ARRIVED': self
.handle_event_stanza_arrived
,
2303 'STANZA_SENT': self
.handle_event_stanza_sent
,
2304 'HTTP_AUTH': self
.handle_event_http_auth
,
2305 'VCARD_PUBLISHED': self
.handle_event_vcard_published
,
2306 'VCARD_NOT_PUBLISHED': self
.handle_event_vcard_not_published
,
2307 'ASK_NEW_NICK': self
.handle_event_ask_new_nick
,
2308 'SIGNED_IN': self
.handle_event_signed_in
,
2309 'METACONTACTS': self
.handle_event_metacontacts
,
2310 'ATOM_ENTRY': self
.handle_atom_entry
,
2311 'FAILED_DECRYPT': self
.handle_event_failed_decrypt
,
2312 'PRIVACY_LISTS_RECEIVED': self
.handle_event_privacy_lists_received
,
2313 'PRIVACY_LIST_RECEIVED': self
.handle_event_privacy_list_received
,
2314 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
2315 self
.handle_event_privacy_lists_active_default
,
2316 'PRIVACY_LIST_REMOVED': self
.handle_event_privacy_list_removed
,
2317 'ZC_NAME_CONFLICT': self
.handle_event_zc_name_conflict
,
2318 'PING_SENT': self
.handle_event_ping_sent
,
2319 'PING_REPLY': self
.handle_event_ping_reply
,
2320 'PING_ERROR': self
.handle_event_ping_error
,
2321 'SEARCH_FORM': self
.handle_event_search_form
,
2322 'SEARCH_RESULT': self
.handle_event_search_result
,
2323 'RESOURCE_CONFLICT': self
.handle_event_resource_conflict
,
2324 'ROSTERX': self
.handle_event_roster_item_exchange
,
2325 'PEP_CONFIG': self
.handle_event_pep_config
,
2326 'UNIQUE_ROOM_ID_UNSUPPORTED': \
2327 self
.handle_event_unique_room_id_unsupported
,
2328 'UNIQUE_ROOM_ID_SUPPORTED': self
.handle_event_unique_room_id_supported
,
2329 'GPG_PASSWORD_REQUIRED': self
.handle_event_gpg_password_required
,
2330 'GPG_ALWAYS_TRUST': self
.handle_event_gpg_always_trust
,
2331 'PASSWORD_REQUIRED': self
.handle_event_password_required
,
2332 'SSL_ERROR': self
.handle_event_ssl_error
,
2333 'FINGERPRINT_ERROR': self
.handle_event_fingerprint_error
,
2334 'PLAIN_CONNECTION': self
.handle_event_plain_connection
,
2335 'INSECURE_SSL_CONNECTION': self
.handle_event_insecure_ssl_connection
,
2336 'PUBSUB_NODE_REMOVED': self
.handle_event_pubsub_node_removed
,
2337 'PUBSUB_NODE_NOT_REMOVED': self
.handle_event_pubsub_node_not_removed
,
2339 gajim
.handlers
= self
.handlers
2341 ################################################################################
2342 ### Methods dealing with gajim.events
2343 ################################################################################
2345 def add_event(self
, account
, jid
, type_
, event_args
):
2346 '''add an event to the gajim.events var'''
2347 # We add it to the gajim.events queue
2348 # Do we have a queue?
2349 jid
= gajim
.get_jid_without_resource(jid
)
2350 no_queue
= len(gajim
.events
.get_events(account
, jid
)) == 0
2351 # type_ can be gc-invitation file-send-error file-error file-request-error
2352 # file-request file-completed file-stopped
2353 # event_type can be in advancedNotificationWindow.events_list
2354 event_types
= {'file-request': 'ft_request',
2355 'file-completed': 'ft_finished'}
2356 event_type
= event_types
.get(type_
)
2357 show_in_roster
= notify
.get_show_in_roster(event_type
, account
, jid
)
2358 show_in_systray
= notify
.get_show_in_systray(event_type
, account
, jid
)
2359 event
= gajim
.events
.create_event(type_
, event_args
,
2360 show_in_roster
=show_in_roster
,
2361 show_in_systray
=show_in_systray
)
2362 gajim
.events
.add_event(account
, jid
, event
)
2364 self
.roster
.show_title()
2365 if no_queue
: # We didn't have a queue: we change icons
2366 if not gajim
.contacts
.get_contact_with_highest_priority(account
, jid
):
2367 if type_
== 'gc-invitation':
2368 self
.roster
.add_groupchat(jid
, account
, status
='offline')
2370 # add contact to roster ("Not In The Roster") if he is not
2371 self
.roster
.add_to_not_in_the_roster(account
, jid
)
2373 self
.roster
.draw_contact(jid
, account
)
2375 # Select the big brother contact in roster, it's visible because it has
2377 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
2379 nearby_family
, bb_jid
, bb_account
= \
2380 self
.roster
._get
_nearby
_family
_and
_big
_brother
(family
, account
)
2382 bb_jid
, bb_account
= jid
, account
2383 self
.roster
.select_contact(bb_jid
, bb_account
)
2385 def handle_event(self
, account
, fjid
, type_
):
2390 resource
= gajim
.get_resource_from_jid(fjid
)
2391 jid
= gajim
.get_jid_without_resource(fjid
)
2393 if type_
in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
2394 w
= self
.msg_win_mgr
.get_window(jid
, account
)
2395 if jid
in self
.minimized_controls
[account
]:
2396 self
.roster
.on_groupchat_maximized(None, jid
, account
)
2399 ctrl
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
2401 elif type_
in ('printed_chat', 'chat', ''):
2402 # '' is for log in/out notifications
2405 event
= gajim
.events
.get_first_event(account
, fjid
, type_
)
2407 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
2411 if type_
== 'printed_chat':
2412 ctrl
= event
.parameters
[0]
2413 elif type_
== 'chat':
2414 session
= event
.parameters
[8]
2415 ctrl
= session
.control
2417 ctrl
= self
.msg_win_mgr
.get_control(fjid
, account
)
2420 highest_contact
= gajim
.contacts
.get_contact_with_highest_priority(
2422 # jid can have a window if this resource was lower when he sent
2423 # message and is now higher because the other one is offline
2424 if resource
and highest_contact
.resource
== resource
and \
2425 not self
.msg_win_mgr
.has_window(jid
, account
):
2426 # remove resource of events too
2427 gajim
.events
.change_jid(account
, fjid
, jid
)
2432 contact
= gajim
.contacts
.get_contact(account
, jid
, resource
)
2434 contact
= highest_contact
2436 ctrl
= self
.new_chat(contact
, account
, resource
= resource
, session
= session
)
2438 gajim
.last_message_time
[account
][jid
] = 0 # long time ago
2441 elif type_
in ('printed_pm', 'pm'):
2442 # assume that the most recently updated control we have for this party
2443 # is the one that this event was in
2444 event
= gajim
.events
.get_first_event(account
, fjid
, type_
)
2446 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
2448 if type_
== 'printed_pm':
2449 ctrl
= event
.parameters
[0]
2451 session
= event
.parameters
[8]
2453 if session
and session
.control
:
2454 ctrl
= session
.control
2458 gc_contact
= gajim
.contacts
.get_gc_contact(account
, room_jid
,
2461 show
= gc_contact
.show
2464 gc_contact
= gajim
.contacts
.create_gc_contact(
2465 room_jid
= room_jid
, name
= nick
, show
= show
)
2468 session
= gajim
.connections
[account
].make_new_session(
2469 fjid
, None, type_
='pm')
2471 self
.new_private_chat(gc_contact
, account
, session
=session
)
2472 ctrl
= session
.control
2475 elif type_
in ('normal', 'file-request', 'file-request-error',
2476 'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
2477 # Get the first single message event
2478 event
= gajim
.events
.get_first_event(account
, fjid
, type_
)
2480 # default to jid without resource
2481 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
2485 self
.roster
.open_event(account
, jid
, event
)
2488 self
.roster
.open_event(account
, fjid
, event
)
2489 elif type_
== 'gmail':
2490 url
=gajim
.connections
[account
].gmail_url
2492 helpers
.launch_browser_mailer('url', url
)
2493 elif type_
== 'gc-invitation':
2494 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
2495 data
= event
.parameters
2496 dialogs
.InvitationReceivedDialog(account
, data
[0], jid
, data
[2],
2498 gajim
.events
.remove_events(account
, jid
, event
)
2499 self
.roster
.draw_contact(jid
, account
)
2500 elif type_
== 'subscription_request':
2501 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
2502 data
= event
.parameters
2503 dialogs
.SubscriptionRequestWindow(jid
, data
[0], account
, data
[1])
2504 gajim
.events
.remove_events(account
, jid
, event
)
2505 self
.roster
.draw_contact(jid
, account
)
2506 elif type_
== 'unsubscribed':
2507 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
2508 contact
= event
.parameters
2509 self
.show_unsubscribed_dialog(account
, contact
)
2510 gajim
.events
.remove_events(account
, jid
, event
)
2511 self
.roster
.draw_contact(jid
, account
)
2513 w
.set_active_tab(ctrl
)
2514 w
.window
.window
.focus()
2515 # Using isinstance here because we want to catch all derived types
2516 if isinstance(ctrl
, ChatControlBase
):
2517 tv
= ctrl
.conv_textview
2520 ################################################################################
2521 ### Methods dealing with emoticons
2522 ################################################################################
2524 def image_is_ok(self
, image
):
2525 if not os
.path
.exists(image
):
2529 img
.set_from_file(image
)
2532 t
= img
.get_storage_type()
2533 if t
!= gtk
.IMAGE_PIXBUF
and t
!= gtk
.IMAGE_ANIMATION
:
2538 def basic_pattern_re(self
):
2540 return self
._basic
_pattern
_re
2541 except AttributeError:
2542 self
._basic
_pattern
_re
= re
.compile(self
.basic_pattern
, re
.IGNORECASE
)
2543 return self
._basic
_pattern
_re
2546 def emot_and_basic_re(self
):
2548 return self
._emot
_and
_basic
_re
2549 except AttributeError:
2550 self
._emot
_and
_basic
_re
= re
.compile(self
.emot_and_basic
,
2551 re
.IGNORECASE
+ re
.UNICODE
)
2552 return self
._emot
_and
_basic
_re
2555 def sth_at_sth_dot_sth_re(self
):
2557 return self
._sth
_at
_sth
_dot
_sth
_re
2558 except AttributeError:
2559 self
._sth
_at
_sth
_dot
_sth
_re
= re
.compile(self
.sth_at_sth_dot_sth
)
2560 return self
._sth
_at
_sth
_dot
_sth
_re
2563 def invalid_XML_chars_re(self
):
2565 return self
._invalid
_XML
_chars
_re
2566 except AttributeError:
2567 self
._invalid
_XML
_chars
_re
= re
.compile(self
.invalid_XML_chars
)
2568 return self
._invalid
_XML
_chars
_re
2570 def make_regexps(self
):
2571 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
2572 # one escapes the metachars with \
2573 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
2574 # \s matches any whitespace character
2575 # \w any alphanumeric character
2576 # \W any non-alphanumeric character
2577 # \b means word boundary. This is a zero-width assertion that
2578 # matches only at the beginning or end of a word.
2579 # ^ matches at the beginning of lines
2581 # * means 0 or more times
2582 # + means 1 or more times
2583 # ? means 0 or 1 time
2585 # [^*] anything but '*' (inside [] you don't have to escape metachars)
2586 # [^\s*] anything but whitespaces and '*'
2587 # (?<!\S) is a one char lookbehind assertion and asks for any leading whitespace
2588 # and mathces beginning of lines so we have correct formatting detection
2589 # even if the the text is just '*foo*'
2590 # (?!\S) is the same thing but it's a lookahead assertion
2591 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at the end
2592 # so http://be) will match http://be and http://be)be) will match http://be)be
2594 legacy_prefixes
= r
"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
2595 r
"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
2596 r
"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
2597 # NOTE: it's ok to catch www.gr such stuff exist!
2599 #FIXME: recognize xmpp: and treat it specially
2600 links
= r
"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
2601 r
"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
2602 r
"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
2604 #2nd one: at_least_one_char@at_least_one_char.at_least_one_char
2605 mail
= r
'\bmailto:\S*[^\s\W]|' r
'\b\S+@\S+\.\S*[^\s\W]'
2607 #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
2608 #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
2609 formatting
= r
'|(?<!\w)' r
'\*[^\s*]' r
'([^*]*[^\s*])?' r
'\*(?!\w)|'\
2610 r
'(?<!\w|\<)' r
'/[^\s/]' r
'([^/]*[^\s/])?' r
'/(?!\w)|'\
2611 r
'(?<!\w)' r
'_[^\s_]' r
'([^_]*[^\s_])?' r
'_(?!\w)'
2613 latex
= r
'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
2615 basic_pattern
= links
+ '|' + mail
+ '|' + legacy_prefixes
2617 link_pattern
= basic_pattern
2618 self
.link_pattern_re
= re
.compile(link_pattern
, re
.IGNORECASE
)
2620 if gajim
.config
.get('use_latex'):
2621 basic_pattern
+= latex
2623 if gajim
.config
.get('ascii_formatting'):
2624 basic_pattern
+= formatting
2625 self
.basic_pattern
= basic_pattern
2627 emoticons_pattern
= ''
2628 if gajim
.config
.get('emoticons_theme'):
2629 # When an emoticon is bordered by an alpha-numeric character it is NOT
2630 # expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
2631 # We still allow multiple emoticons side-by-side like :P:P:P
2632 # sort keys by length so :qwe emot is checked before :q
2633 keys
= sorted(self
.emoticons
, key
=len, reverse
=True)
2634 emoticons_pattern_prematch
= ''
2635 emoticons_pattern_postmatch
= ''
2637 for emoticon
in keys
: # travel thru emoticons list
2638 emoticon
= emoticon
.decode('utf-8')
2639 emoticon_escaped
= re
.escape(emoticon
) # espace regexp metachars
2640 emoticons_pattern
+= emoticon_escaped
+ '|'# | means or in regexp
2641 if (emoticon_length
!= len(emoticon
)):
2642 # Build up expressions to match emoticons next to other emoticons
2643 emoticons_pattern_prematch
= emoticons_pattern_prematch
[:-1] + ')|(?<='
2644 emoticons_pattern_postmatch
= emoticons_pattern_postmatch
[:-1] + ')|(?='
2645 emoticon_length
= len(emoticon
)
2646 emoticons_pattern_prematch
+= emoticon_escaped
+ '|'
2647 emoticons_pattern_postmatch
+= emoticon_escaped
+ '|'
2648 # We match from our list of emoticons, but they must either have
2649 # whitespace, or another emoticon next to it to match successfully
2650 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
2651 emoticons_pattern
= '|' + \
2652 '(?:(?<![\w.]' + emoticons_pattern_prematch
[:-1] + '))' + \
2653 '(?:' + emoticons_pattern
[:-1] + ')' + \
2654 '(?:(?![\w]' + emoticons_pattern_postmatch
[:-1] + '))'
2656 # because emoticons match later (in the string) they need to be after
2657 # basic matches that may occur earlier
2658 self
.emot_and_basic
= basic_pattern
+ emoticons_pattern
2660 # needed for xhtml display
2661 self
.emot_only
= emoticons_pattern
2663 # at least one character in 3 parts (before @, after @, after .)
2664 self
.sth_at_sth_dot_sth
= r
'\S+@\S+\.\S*[^\s)?]'
2667 self
.invalid_XML_chars
= u
'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]'
2669 def popup_emoticons_under_button(self
, button
, parent_win
):
2670 ''' pops emoticons menu under button, located in parent_win'''
2671 gtkgui_helpers
.popup_emoticons_under_button(self
.emoticons_menu
,
2674 def prepare_emoticons_menu(self
):
2676 def emoticon_clicked(w
, str_
):
2677 if self
.emoticon_menuitem_clicked
:
2678 self
.emoticon_menuitem_clicked(str_
)
2679 # don't keep reference to CB of object
2680 # this will prevent making it uncollectable
2681 self
.emoticon_menuitem_clicked
= None
2682 def selection_done(widget
):
2683 # remove reference to CB of object, which will
2684 # make it uncollectable
2685 self
.emoticon_menuitem_clicked
= None
2687 # Calculate the side lenght of the popup to make it a square
2688 size
= int(round(math
.sqrt(len(self
.emoticons_images
))))
2689 for image
in self
.emoticons_images
:
2690 item
= gtk
.MenuItem()
2692 if isinstance(image
[1], gtk
.gdk
.PixbufAnimation
):
2693 img
.set_from_animation(image
[1])
2695 img
.set_from_pixbuf(image
[1])
2697 item
.connect('activate', emoticon_clicked
, image
[0])
2698 #FIXME: add tooltip with ascii
2699 menu
.attach(item
, counter
% size
, counter
% size
+ 1,
2700 counter
/ size
, counter
/ size
+ 1)
2702 menu
.connect('selection-done', selection_done
)
2706 def _init_emoticons(self
, path
, need_reload
= False):
2707 #initialize emoticons dictionary and unique images list
2708 self
.emoticons_images
= list()
2709 self
.emoticons
= dict()
2710 self
.emoticons_animations
= dict()
2712 sys
.path
.append(path
)
2715 # we need to reload else that doesn't work when changing emoticon set
2717 emots
= emoticons
.emoticons
2718 for emot_filename
in emots
:
2719 emot_file
= os
.path
.join(path
, emot_filename
)
2720 if not self
.image_is_ok(emot_file
):
2722 for emot
in emots
[emot_filename
]:
2723 emot
= emot
.decode('utf-8')
2724 # This avoids duplicated emoticons with the same image eg. :) and :-)
2725 if not emot_file
in self
.emoticons
.values():
2726 if emot_file
.endswith('.gif'):
2727 pix
= gtk
.gdk
.PixbufAnimation(emot_file
)
2729 pix
= gtk
.gdk
.pixbuf_new_from_file_at_size(emot_file
, 16, 16)
2730 self
.emoticons_images
.append((emot
, pix
))
2731 self
.emoticons
[emot
.upper()] = emot_file
2733 sys
.path
.remove(path
)
2735 def init_emoticons(self
, need_reload
= False):
2736 emot_theme
= gajim
.config
.get('emoticons_theme')
2740 path
= os
.path
.join(gajim
.DATA_DIR
, 'emoticons', emot_theme
)
2741 if not os
.path
.exists(path
):
2742 # It's maybe a user theme
2743 path
= os
.path
.join(gajim
.MY_EMOTS_PATH
, emot_theme
)
2744 if not os
.path
.exists(path
): # theme doesn't exist, disable emoticons
2745 dialogs
.WarningDialog(_('Emoticons disabled'),
2746 _('Your configured emoticons theme has not been found, so emoticons have been disabled.'))
2747 gajim
.config
.set('emoticons_theme', '')
2749 self
._init
_emoticons
(path
, need_reload
)
2750 if len(self
.emoticons
) == 0:
2751 # maybe old format of emoticons file, try to convert it
2755 emots
= emoticons
.emoticons
2756 fd
= open(os
.path
.join(path
, 'emoticons.py'), 'w')
2757 fd
.write('emoticons = ')
2758 pprint
.pprint( dict([
2759 (file_
, [i
for i
in emots
.keys() if emots
[i
] == file_
])
2760 for file_
in set(emots
.values())]), fd
)
2763 self
._init
_emoticons
(path
, need_reload
=True)
2766 if len(self
.emoticons
) == 0:
2767 dialogs
.WarningDialog(_('Emoticons disabled'),
2768 _('Your configured emoticons theme cannot been loaded. You maybe need to update the format of emoticons.py file. See http://trac.gajim.org/wiki/Emoticons for more details.'))
2769 if self
.emoticons_menu
:
2770 self
.emoticons_menu
.destroy()
2771 self
.emoticons_menu
= self
.prepare_emoticons_menu()
2773 ################################################################################
2774 ### Methods for opening new messages controls
2775 ################################################################################
2777 def join_gc_room(self
, account
, room_jid
, nick
, password
, minimize
=False,
2778 is_continued
=False):
2779 '''joins the room immediately'''
2781 nick
= gajim
.nicks
[account
]
2783 if self
.msg_win_mgr
.has_window(room_jid
, account
) and \
2784 gajim
.gc_connected
[account
][room_jid
]:
2785 gc_ctrl
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
2786 win
= gc_ctrl
.parent_win
2787 win
.set_active_tab(gc_ctrl
)
2788 dialogs
.ErrorDialog(_('You are already in group chat %s') % room_jid
)
2791 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
2792 if gajim
.connections
[account
].connected
== invisible_show
:
2793 dialogs
.ErrorDialog(
2794 _('You cannot join a group chat while you are invisible'))
2797 minimized_control
= gajim
.interface
.minimized_controls
[account
].get(
2800 if minimized_control
is None and not self
.msg_win_mgr
.has_window(room_jid
,
2802 # Join new groupchat
2804 contact
= gajim
.contacts
.create_contact(jid
=room_jid
, name
=nick
)
2805 gc_control
= GroupchatControl(None, contact
, account
)
2806 gajim
.interface
.minimized_controls
[account
][room_jid
] = gc_control
2807 self
.roster
.add_groupchat(room_jid
, account
)
2809 self
.new_room(room_jid
, nick
, account
, is_continued
=is_continued
)
2810 elif minimized_control
is None:
2811 # We are already in that groupchat
2812 gc_control
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
2813 gc_control
.nick
= nick
2814 gc_control
.parent_win
.set_active_tab(gc_control
)
2816 # We are already in this groupchat and it is minimized
2817 minimized_control
.nick
= nick
2818 self
.roster
.add_groupchat(room_jid
, account
)
2821 gajim
.connections
[account
].join_gc(nick
, room_jid
, password
)
2823 gajim
.gc_passwords
[room_jid
] = password
2825 def new_room(self
, room_jid
, nick
, account
, is_continued
=False):
2826 # Get target window, create a control, and associate it with the window
2827 contact
= gajim
.contacts
.create_contact(jid
=room_jid
, name
=nick
)
2828 mw
= self
.msg_win_mgr
.get_window(contact
.jid
, account
)
2830 mw
= self
.msg_win_mgr
.create_window(contact
, account
,
2831 GroupchatControl
.TYPE_ID
)
2832 gc_control
= GroupchatControl(mw
, contact
, account
,
2833 is_continued
=is_continued
)
2834 mw
.new_tab(gc_control
)
2836 def new_private_chat(self
, gc_contact
, account
, session
=None):
2837 contact
= gajim
.contacts
.contact_from_gc_contact(gc_contact
)
2838 type_
= message_control
.TYPE_PM
2839 fjid
= gc_contact
.room_jid
+ '/' + gc_contact
.name
2841 conn
= gajim
.connections
[account
]
2843 if not session
and fjid
in conn
.sessions
:
2844 sessions
= [s
for s
in conn
.sessions
[fjid
].values() if isinstance(s
, ChatControlSession
)]
2846 # look for an existing session with a chat control
2852 if not session
and not len(sessions
) == 0:
2853 # there are no sessions with chat controls, just take the first one
2854 session
= sessions
[0]
2857 # couldn't find an existing ChatControlSession, just make a new one
2859 session
= conn
.make_new_session(fjid
, None, 'pm')
2861 if not session
.control
:
2862 mw
= self
.msg_win_mgr
.get_window(fjid
, account
)
2864 mw
= self
.msg_win_mgr
.create_window(contact
, account
, type_
)
2866 session
.control
= PrivateChatControl(mw
, gc_contact
, contact
, account
,
2868 mw
.new_tab(session
.control
)
2870 if len(gajim
.events
.get_events(account
, fjid
)):
2871 # We call this here to avoid race conditions with widget validation
2872 session
.control
.read_queue()
2874 return session
.control
2876 def new_chat(self
, contact
, account
, resource
=None, session
=None):
2877 # Get target window, create a control, and associate it with the window
2878 type_
= message_control
.TYPE_CHAT
2882 fjid
+= '/' + resource
2884 mw
= self
.msg_win_mgr
.get_window(fjid
, account
)
2886 mw
= self
.msg_win_mgr
.create_window(contact
, account
, type_
, resource
)
2888 chat_control
= ChatControl(mw
, contact
, account
, session
, resource
)
2890 mw
.new_tab(chat_control
)
2892 if len(gajim
.events
.get_events(account
, fjid
)):
2893 # We call this here to avoid race conditions with widget validation
2894 chat_control
.read_queue()
2898 def new_chat_from_jid(self
, account
, fjid
, message
=None):
2899 jid
, resource
= gajim
.get_room_and_nick_from_fjid(fjid
)
2900 contact
= gajim
.contacts
.get_contact(account
, jid
, resource
)
2901 added_to_roster
= False
2903 added_to_roster
= True
2904 contact
= self
.roster
.add_to_not_in_the_roster(account
, jid
,
2907 ctrl
= self
.msg_win_mgr
.get_control(fjid
, account
)
2910 ctrl
= self
.new_chat(contact
, account
,
2912 if len(gajim
.events
.get_events(account
, fjid
)):
2916 buffer = ctrl
.msg_textview
.get_buffer()
2917 buffer.set_text(message
)
2918 mw
= ctrl
.parent_win
2919 mw
.set_active_tab(ctrl
)
2922 ctrl
.user_nick
= gajim
.nicks
[account
]
2923 gobject
.idle_add(lambda: mw
.window
.grab_focus())
2925 def on_open_chat_window(self
, widget
, contact
, account
, resource
=None,
2928 # Get the window containing the chat
2932 fjid
+= '/' + resource
2937 ctrl
= session
.control
2939 win
= self
.msg_win_mgr
.get_window(fjid
, account
)
2942 ctrl
= win
.get_control(fjid
, account
)
2945 ctrl
= self
.new_chat(contact
, account
, resource
=resource
,
2947 # last message is long time ago
2948 gajim
.last_message_time
[account
][ctrl
.get_full_jid()] = 0
2950 win
= ctrl
.parent_win
2952 win
.set_active_tab(ctrl
)
2954 if gajim
.connections
[account
].is_zeroconf
and \
2955 gajim
.connections
[account
].status
in ('offline', 'invisible'):
2956 ctrl
= win
.get_control(fjid
, account
)
2958 ctrl
.got_disconnected()
2960 ################################################################################
2962 ################################################################################
2964 def read_sleepy(self
):
2965 '''Check idle status and change that status if needed'''
2966 if not self
.sleeper
.poll():
2967 # idle detection is not supported in that OS
2968 return False # stop looping in vain
2969 state
= self
.sleeper
.getState()
2970 for account
in gajim
.connections
:
2971 if account
not in gajim
.sleeper_state
or \
2972 not gajim
.sleeper_state
[account
]:
2974 if state
== common
.sleepy
.STATE_AWAKE
and \
2975 gajim
.sleeper_state
[account
] in ('autoaway', 'autoxa'):
2977 self
.roster
.send_status(account
, 'online',
2978 gajim
.status_before_autoaway
[account
])
2979 gajim
.status_before_autoaway
[account
] = ''
2980 gajim
.sleeper_state
[account
] = 'online'
2981 elif state
== common
.sleepy
.STATE_AWAY
and \
2982 gajim
.sleeper_state
[account
] == 'online' and \
2983 gajim
.config
.get('autoaway'):
2984 # we save out online status
2985 gajim
.status_before_autoaway
[account
] = \
2986 gajim
.connections
[account
].status
2987 # we go away (no auto status) [we pass True to auto param]
2988 auto_message
= gajim
.config
.get('autoaway_message')
2989 if not auto_message
:
2990 auto_message
= gajim
.connections
[account
].status
2992 auto_message
= auto_message
.replace('$S','%(status)s')
2993 auto_message
= auto_message
.replace('$T','%(time)s')
2994 auto_message
= auto_message
% {
2995 'status': gajim
.status_before_autoaway
[account
],
2996 'time': gajim
.config
.get('autoawaytime')
2998 self
.roster
.send_status(account
, 'away', auto_message
, auto
=True)
2999 gajim
.sleeper_state
[account
] = 'autoaway'
3000 elif state
== common
.sleepy
.STATE_XA
and \
3001 gajim
.sleeper_state
[account
] in ('online', 'autoaway',
3002 'autoaway-forced') and gajim
.config
.get('autoxa'):
3003 # we go extended away [we pass True to auto param]
3004 auto_message
= gajim
.config
.get('autoxa_message')
3005 if not auto_message
:
3006 auto_message
= gajim
.connections
[account
].status
3008 auto_message
= auto_message
.replace('$S','%(status)s')
3009 auto_message
= auto_message
.replace('$T','%(time)s')
3010 auto_message
= auto_message
% {
3011 'status': gajim
.status_before_autoaway
[account
],
3012 'time': gajim
.config
.get('autoxatime')
3014 self
.roster
.send_status(account
, 'xa', auto_message
, auto
=True)
3015 gajim
.sleeper_state
[account
] = 'autoxa'
3016 return True # renew timeout (loop for ever)
3018 def autoconnect(self
):
3019 '''auto connect at startup'''
3020 # dict of account that want to connect sorted by status
3022 for a
in gajim
.connections
:
3023 if gajim
.config
.get_per('accounts', a
, 'autoconnect'):
3024 if gajim
.config
.get_per('accounts', a
, 'restore_last_status'):
3025 self
.roster
.send_status(a
, gajim
.config
.get_per('accounts', a
,
3026 'last_status'), helpers
.from_one_line(gajim
.config
.get_per(
3027 'accounts', a
, 'last_status_msg')))
3029 show
= gajim
.config
.get_per('accounts', a
, 'autoconnect_as')
3030 if not show
in gajim
.SHOW_LIST
:
3032 if not show
in shows
:
3035 shows
[show
].append(a
)
3036 def on_message(message
, pep_dict
):
3039 for a
in shows
[show
]:
3040 self
.roster
.send_status(a
, show
, message
)
3041 self
.roster
.send_pep(a
, pep_dict
)
3043 message
= self
.roster
.get_status_message(show
, on_message
)
3046 def show_systray(self
):
3047 self
.systray_enabled
= True
3048 self
.systray
.show_icon()
3050 def hide_systray(self
):
3051 self
.systray_enabled
= False
3052 self
.systray
.hide_icon()
3054 def on_launch_browser_mailer(self
, widget
, url
, kind
):
3055 helpers
.launch_browser_mailer(kind
, url
)
3057 def process_connections(self
):
3058 ''' Called each foo (200) miliseconds. Check for idlequeue timeouts. '''
3060 gajim
.idlequeue
.process()
3062 # Otherwise, an exception will stop our loop
3063 timeout
, in_seconds
= gajim
.idlequeue
.PROCESS_TIMEOUT
3065 gobject
.timeout_add_seconds(timeout
, self
.process_connections
)
3067 gobject
.timeout_add(timeout
, self
.process_connections
)
3069 return True # renew timeout (loop for ever)
3071 def save_config(self
):
3072 err_str
= parser
.write()
3073 if err_str
is not None:
3074 print >> sys
.stderr
, err_str
3075 # it is good to notify the user
3076 # in case he or she cannot see the output of the console
3077 dialogs
.ErrorDialog(_('Could not save your settings and preferences'),
3081 def save_avatar_files(self
, jid
, photo
, puny_nick
= None, local
= False):
3082 '''Saves an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image.'''
3083 puny_jid
= helpers
.sanitize_filename(jid
)
3084 path_to_file
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
)
3086 path_to_file
= os
.path
.join(path_to_file
, puny_nick
)
3087 # remove old avatars
3088 for typ
in ('jpeg', 'png'):
3090 path_to_original_file
= path_to_file
+ '_local'+ '.' + typ
3092 path_to_original_file
= path_to_file
+ '.' + typ
3093 if os
.path
.isfile(path_to_original_file
):
3094 os
.remove(path_to_original_file
)
3098 extension
= '_local.png' # save local avatars as png file
3100 pixbuf
, typ
= gtkgui_helpers
.get_pixbuf_from_data(photo
, want_type
= True)
3103 extension
= '.' + typ
3104 if typ
not in ('jpeg', 'png'):
3105 gajim
.log
.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid
, typ
))
3108 path_to_original_file
= path_to_file
+ extension
3110 pixbuf
.save(path_to_original_file
, typ
)
3111 except Exception, e
:
3112 log
.error('Error writing avatar file %s: %s' % (path_to_original_file
,
3114 # Generate and save the resized, color avatar
3115 pixbuf
= gtkgui_helpers
.get_scaled_pixbuf(pixbuf
, 'notification')
3117 path_to_normal_file
= path_to_file
+ '_notif_size_colored' + extension
3119 pixbuf
.save(path_to_normal_file
, 'png')
3120 except Exception, e
:
3121 log
.error('Error writing avatar file %s: %s' % \
3122 (path_to_original_file
, str(e
)))
3123 # Generate and save the resized, black and white avatar
3124 bwbuf
= gtkgui_helpers
.get_scaled_pixbuf(
3125 gtkgui_helpers
.make_pixbuf_grayscale(pixbuf
), 'notification')
3127 path_to_bw_file
= path_to_file
+ '_notif_size_bw' + extension
3129 bwbuf
.save(path_to_bw_file
, 'png')
3130 except Exception, e
:
3131 log
.error('Error writing avatar file %s: %s' % \
3132 (path_to_original_file
, str(e
)))
3134 def remove_avatar_files(self
, jid
, puny_nick
= None, local
= False):
3135 '''remove avatar files of a jid'''
3136 puny_jid
= helpers
.sanitize_filename(jid
)
3137 path_to_file
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
)
3139 path_to_file
= os
.path
.join(path_to_file
, puny_nick
)
3140 for ext
in ('.jpeg', '.png'):
3142 ext
= '_local' + ext
3143 path_to_original_file
= path_to_file
+ ext
3144 if os
.path
.isfile(path_to_file
+ ext
):
3145 os
.remove(path_to_file
+ ext
)
3146 if os
.path
.isfile(path_to_file
+ '_notif_size_colored' + ext
):
3147 os
.remove(path_to_file
+ '_notif_size_colored' + ext
)
3148 if os
.path
.isfile(path_to_file
+ '_notif_size_bw' + ext
):
3149 os
.remove(path_to_file
+ '_notif_size_bw' + ext
)
3151 def auto_join_bookmarks(self
, account
):
3152 '''autojoin bookmarked GCs that have 'auto join' on for this account'''
3153 for bm
in gajim
.connections
[account
].bookmarks
:
3154 if bm
['autojoin'] in ('1', 'true'):
3156 # Only join non-opened groupchats. Opened one are already
3157 # auto-joined on re-connection
3158 if not jid
in gajim
.gc_connected
[account
]:
3159 # we are not already connected
3160 minimize
= bm
['minimize'] in ('1', 'true')
3161 gajim
.interface
.join_gc_room(account
, jid
, bm
['nick'],
3162 bm
['password'], minimize
= minimize
)
3163 elif jid
in self
.minimized_controls
[account
]:
3164 # more or less a hack:
3165 # On disconnect the minimized gc contact instances
3166 # were set to offline. Reconnect them to show up in the roster.
3167 self
.roster
.add_groupchat(jid
, account
)
3169 def add_gc_bookmark(self
, account
, name
, jid
, autojoin
, minimize
, password
,
3171 '''add a bookmark for this account, sorted in bookmark list'''
3175 'autojoin': autojoin
,
3176 'minimize': minimize
,
3177 'password': password
,
3182 # check for duplicate entry and respect alpha order
3183 for bookmark
in gajim
.connections
[account
].bookmarks
:
3184 if bookmark
['jid'] == bm
['jid']:
3185 dialogs
.ErrorDialog(
3186 _('Bookmark already set'),
3187 _('Group Chat "%s" is already in your bookmarks.') % bm
['jid'])
3189 if bookmark
['name'] > bm
['name']:
3194 gajim
.connections
[account
].bookmarks
.insert(index
, bm
)
3196 gajim
.connections
[account
].bookmarks
.append(bm
)
3197 gajim
.connections
[account
].store_bookmarks()
3198 self
.roster
.set_actions_menu_needs_rebuild()
3199 dialogs
.InformationDialog(
3200 _('Bookmark has been added successfully'),
3201 _('You can manage your bookmarks via Actions menu in your roster.'))
3204 # does JID exist only within a groupchat?
3205 def is_pm_contact(self
, fjid
, account
):
3206 bare_jid
= gajim
.get_jid_without_resource(fjid
)
3208 gc_ctrl
= self
.msg_win_mgr
.get_gc_control(bare_jid
, account
)
3210 if not gc_ctrl
and \
3211 bare_jid
in self
.minimized_controls
[account
]:
3212 gc_ctrl
= self
.minimized_controls
[account
][bare_jid
]
3214 return gc_ctrl
and gc_ctrl
.type_id
== message_control
.TYPE_GC
3216 def create_ipython_window(self
):
3218 from ipython_view
import IPythonView
3220 print 'ipython_view not found'
3225 font
= 'Lucida Console 9'
3227 font
= 'Luxi Mono 10'
3229 window
= gtk
.Window()
3230 window
.set_size_request(750,550)
3231 window
.set_resizable(True)
3232 sw
= gtk
.ScrolledWindow()
3233 sw
.set_policy(gtk
.POLICY_AUTOMATIC
,gtk
.POLICY_AUTOMATIC
)
3234 view
= IPythonView()
3235 view
.modify_font(pango
.FontDescription(font
))
3236 view
.set_wrap_mode(gtk
.WRAP_CHAR
)
3240 def on_delete(win
, event
):
3243 window
.connect('delete_event',on_delete
)
3244 view
.updateNamespace({'gajim': gajim
})
3245 gajim
.ipython_window
= window
3248 gajim
.interface
= self
3249 gajim
.thread_interface
= ThreadInterface
3250 # This is the manager and factory of message windows set by the module
3251 self
.msg_win_mgr
= None
3252 self
.jabber_state_images
= {'16': {}, '32': {}, 'opened': {},
3254 self
.emoticons_menu
= None
3255 # handler when an emoticon is clicked in emoticons_menu
3256 self
.emoticon_menuitem_clicked
= None
3257 self
.minimized_controls
= {}
3258 self
.status_sent_to_users
= {}
3259 self
.status_sent_to_groups
= {}
3260 self
.gpg_passphrase
= {}
3261 self
.pass_dialog
= {}
3262 self
.default_colors
= {
3263 'inmsgcolor': gajim
.config
.get('inmsgcolor'),
3264 'outmsgcolor': gajim
.config
.get('outmsgcolor'),
3265 'statusmsgcolor': gajim
.config
.get('statusmsgcolor'),
3266 'urlmsgcolor': gajim
.config
.get('urlmsgcolor'),
3269 cfg_was_read
= parser
.read()
3270 # override logging settings from config (don't take care of '-q' option)
3271 if gajim
.config
.get('verbose'):
3272 logging_helpers
.set_verbose()
3274 # Is Gajim default app?
3275 if os
.name
!= 'nt' and gajim
.config
.get('check_if_gajim_is_default'):
3276 gtkgui_helpers
.possibly_set_gajim_as_xmpp_handler()
3278 for account
in gajim
.config
.get_per('accounts'):
3279 if gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
3280 gajim
.ZEROCONF_ACC_NAME
= account
3282 # Is gnome configured to activate row on single click ?
3285 client
= gconf
.client_get_default()
3286 click_policy
= client
.get_string(
3287 '/apps/nautilus/preferences/click_policy')
3288 if click_policy
== 'single':
3289 gajim
.single_click
= True
3292 # add default status messages if there is not in the config file
3293 if len(gajim
.config
.get_per('statusmsg')) == 0:
3294 default
= gajim
.config
.statusmsg_default
3296 gajim
.config
.add_per('statusmsg', msg
)
3297 gajim
.config
.set_per('statusmsg', msg
, 'message', default
[msg
][0])
3298 gajim
.config
.set_per('statusmsg', msg
, 'activity', default
[msg
][1])
3299 gajim
.config
.set_per('statusmsg', msg
, 'subactivity',
3301 gajim
.config
.set_per('statusmsg', msg
, 'activity_text',
3303 gajim
.config
.set_per('statusmsg', msg
, 'mood', default
[msg
][4])
3304 gajim
.config
.set_per('statusmsg', msg
, 'mood_text', default
[msg
][5])
3305 #add default themes if there is not in the config file
3306 theme
= gajim
.config
.get('roster_theme')
3307 if not theme
in gajim
.config
.get_per('themes'):
3308 gajim
.config
.set('roster_theme', _('default'))
3309 if len(gajim
.config
.get_per('themes')) == 0:
3310 d
= ['accounttextcolor', 'accountbgcolor', 'accountfont',
3311 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
3312 'groupfontattrs', 'contacttextcolor', 'contactbgcolor',
3313 'contactfont', 'contactfontattrs', 'bannertextcolor',
3316 default
= gajim
.config
.themes_default
3317 for theme_name
in default
:
3318 gajim
.config
.add_per('themes', theme_name
)
3319 theme
= default
[theme_name
]
3321 gajim
.config
.set_per('themes', theme_name
, o
,
3324 if gajim
.config
.get('autodetect_browser_mailer') or not cfg_was_read
:
3325 gtkgui_helpers
.autodetect_browser_mailer()
3327 gajim
.idlequeue
= idlequeue
.get_idlequeue()
3328 # resolve and keep current record of resolved hosts
3329 gajim
.resolver
= resolver
.get_resolver(gajim
.idlequeue
)
3330 gajim
.socks5queue
= socks5
.SocksQueue(gajim
.idlequeue
,
3331 self
.handle_event_file_rcv_completed
,
3332 self
.handle_event_file_progress
,
3333 self
.handle_event_file_error
)
3334 gajim
.proxy65_manager
= proxy65_manager
.Proxy65Manager(gajim
.idlequeue
)
3335 gajim
.default_session_type
= ChatControlSession
3336 self
.register_handlers()
3337 if gajim
.config
.get('enable_zeroconf') and gajim
.HAVE_ZEROCONF
:
3338 gajim
.connections
[gajim
.ZEROCONF_ACC_NAME
] = \
3339 connection_zeroconf
.ConnectionZeroconf(gajim
.ZEROCONF_ACC_NAME
)
3340 for account
in gajim
.config
.get_per('accounts'):
3341 if not gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
3342 gajim
.connections
[account
] = common
.connection
.Connection(account
)
3345 gtk
.about_dialog_set_email_hook(self
.on_launch_browser_mailer
, 'mail')
3346 gtk
.about_dialog_set_url_hook(self
.on_launch_browser_mailer
, 'url')
3347 gtk
.link_button_set_uri_hook(self
.on_launch_browser_mailer
, 'url')
3351 for a
in gajim
.connections
:
3352 self
.instances
[a
] = {'infos': {}, 'disco': {}, 'gc_config': {},
3353 'search': {}, 'online_dialog': {}}
3354 # online_dialog contains all dialogs that have a meaning only when we
3355 # are not disconnected
3356 self
.minimized_controls
[a
] = {}
3357 gajim
.contacts
.add_account(a
)
3358 gajim
.groups
[a
] = {}
3359 gajim
.gc_connected
[a
] = {}
3360 gajim
.automatic_rooms
[a
] = {}
3361 gajim
.newly_added
[a
] = []
3362 gajim
.to_be_removed
[a
] = []
3363 gajim
.nicks
[a
] = gajim
.config
.get_per('accounts', a
, 'name')
3364 gajim
.block_signed_in_notifications
[a
] = True
3365 gajim
.sleeper_state
[a
] = 0
3366 gajim
.encrypted_chats
[a
] = []
3367 gajim
.last_message_time
[a
] = {}
3368 gajim
.status_before_autoaway
[a
] = ''
3369 gajim
.transport_avatar
[a
] = {}
3370 gajim
.gajim_optional_features
[a
] = []
3371 gajim
.caps_hash
[a
] = ''
3373 helpers
.update_optional_features()
3375 self
.remote_ctrl
= None
3377 if gajim
.config
.get('networkmanager_support') and dbus_support
.supported
:
3378 import network_manager_listener
3380 # Handle gnome screensaver
3381 if dbus_support
.supported
:
3382 def gnome_screensaver_ActiveChanged_cb(active
):
3384 for account
in gajim
.connections
:
3385 if gajim
.sleeper_state
[account
] == 'autoaway-forced':
3386 # We came back online ofter gnome-screensaver autoaway
3387 self
.roster
.send_status(account
, 'online',
3388 gajim
.status_before_autoaway
[account
])
3389 gajim
.status_before_autoaway
[account
] = ''
3390 gajim
.sleeper_state
[account
] = 'online'
3392 if not gajim
.config
.get('autoaway'):
3393 # Don't go auto away if user disabled the option
3395 for account
in gajim
.connections
:
3396 if account
not in gajim
.sleeper_state
or \
3397 not gajim
.sleeper_state
[account
]:
3399 if gajim
.sleeper_state
[account
] == 'online':
3400 # we save out online status
3401 gajim
.status_before_autoaway
[account
] = \
3402 gajim
.connections
[account
].status
3403 # we go away (no auto status) [we pass True to auto param]
3404 auto_message
= gajim
.config
.get('autoaway_message')
3405 if not auto_message
:
3406 auto_message
= gajim
.connections
[account
].status
3408 auto_message
= auto_message
.replace('$S','%(status)s')
3409 auto_message
= auto_message
.replace('$T','%(time)s')
3410 auto_message
= auto_message
% {
3411 'status': gajim
.status_before_autoaway
[account
],
3412 'time': gajim
.config
.get('autoxatime')
3414 self
.roster
.send_status(account
, 'away', auto_message
,
3416 gajim
.sleeper_state
[account
] = 'autoaway-forced'
3419 bus
= dbus
.SessionBus()
3420 bus
.add_signal_receiver(gnome_screensaver_ActiveChanged_cb
,
3421 'ActiveChanged', 'org.gnome.ScreenSaver')
3425 self
.show_vcard_when_connect
= []
3427 self
.sleeper
= common
.sleepy
.Sleepy(
3428 gajim
.config
.get('autoawaytime') * 60, # make minutes to seconds
3429 gajim
.config
.get('autoxatime') * 60)
3431 gtkgui_helpers
.make_jabber_state_images()
3433 self
.systray_enabled
= False
3434 self
.systray_capabilities
= False
3436 if (os
.name
== 'nt'):
3438 self
.systray
= statusicon
.StatusIcon()
3439 self
.systray_capabilities
= True
3440 else: # use ours, not GTK+ one
3441 # [FIXME: remove this when we migrate to 2.10 and we can do
3442 # cool tooltips somehow and (not dying to keep) animation]
3444 self
.systray_capabilities
= systray
.HAS_SYSTRAY_CAPABILITIES
3445 if self
.systray_capabilities
:
3446 self
.systray
= systray
.Systray()
3448 if self
.systray_capabilities
and gajim
.config
.get('trayicon') != 'never':
3451 path_to_file
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'gajim.png')
3452 pix
= gtk
.gdk
.pixbuf_new_from_file(path_to_file
)
3453 # set the icon to all windows
3454 gtk
.window_set_default_icon(pix
)
3456 self
.init_emoticons()
3459 self
.roster
= roster_window
.RosterWindow()
3460 for account
in gajim
.connections
:
3461 gajim
.connections
[account
].load_roster_from_db()
3463 # get instances for windows/dialogs that will show_all()/hide()
3464 self
.instances
['file_transfers'] = dialogs
.FileTransfersWindow()
3466 # get transports type from DB
3467 gajim
.transport_type
= gajim
.logger
.get_transports_type()
3469 # test is dictionnary is present for speller
3470 if gajim
.config
.get('use_speller'):
3471 lang
= gajim
.config
.get('speller_language')
3477 spell
= gtkspell
.Spell(tv
, lang
)
3478 except (ImportError, TypeError, RuntimeError, OSError):
3479 dialogs
.AspellDictError(lang
)
3481 if gajim
.config
.get('soundplayer') == '':
3482 # only on first time Gajim starts
3483 commands
= ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
3484 for command
in commands
:
3485 if helpers
.is_in_path(command
):
3486 if command
== 'aplay':
3488 gajim
.config
.set('soundplayer', command
)
3491 self
.last_ftwindow_update
= 0
3493 gobject
.timeout_add(100, self
.autoconnect
)
3494 timeout
, in_seconds
= gajim
.idlequeue
.PROCESS_TIMEOUT
3496 gobject
.timeout_add_seconds(timeout
, self
.process_connections
)
3498 gobject
.timeout_add(timeout
, self
.process_connections
)
3499 gobject
.timeout_add_seconds(gajim
.config
.get(
3500 'check_idle_every_foo_seconds'), self
.read_sleepy
)
3502 # when using libasyncns we need to process resolver in regular intervals
3503 if resolver
.USE_LIBASYNCNS
:
3504 gobject
.timeout_add(200, gajim
.resolver
.process
)
3506 # setup the indicator
3507 if gajim
.HAVE_INDICATOR
:
3508 notify
.setup_indicator_server()
3511 if gajim
.config
.get('remote_control'):
3513 import remote_control
3514 self
.remote_ctrl
= remote_control
.Remote()
3517 gobject
.timeout_add_seconds(5, remote_init
)
3519 if __name__
== '__main__':
3520 def sigint_cb(num
, stack
):
3522 # ^C exits the application normally to delete pid file
3523 signal
.signal(signal
.SIGINT
, sigint_cb
)
3525 log
.info("Encodings: d:%s, fs:%s, p:%s", sys
.getdefaultencoding(), \
3526 sys
.getfilesystemencoding(), locale
.getpreferredencoding())
3529 # Session Management support
3536 gajim
.interface
.roster
.quit_gtkgui_interface()
3537 gnome
.program_init('gajim', gajim
.version
)
3538 cli
= gnome
.ui
.master_client()
3539 cli
.connect('die', die_cb
)
3541 path_to_gajim_script
= gtkgui_helpers
.get_abspath_for_script(
3544 if path_to_gajim_script
:
3545 argv
= [path_to_gajim_script
]
3546 # FIXME: remove this typeerror catch when gnome python is old and
3547 # not bad patched by distro men [2.12.0 + should not need all that
3550 cli
.set_restart_command(argv
)
3551 except AttributeError:
3552 cli
.set_restart_command(len(argv
), argv
)
3554 check_paths
.check_and_possibly_create_paths()
3560 # This makes Gajim unusable under windows, and threads are used only
3561 # for GPG, so not under windows
3562 gtk
.gdk
.threads_init()
3564 except KeyboardInterrupt:
3565 print >> sys
.stderr
, 'KeyboardInterrupt'